第6章 CQSの基本(更新と参照は混ぜない)🔀✅✨
この章は、CQRSの前にやる“準備運動”だよ〜!🏃♀️💨 いきなり「CommandHandler!QueryService!」って分ける前に、まずは 「同じクラスの中でもいいから、更新と参照は混ぜない」 を体に染み込ませます😊🧠✨
今日のゴール🎯💡
できるようになることは3つだけ!🙌
- Command(更新) と Query(参照) を見分けられる👀✅
- メソッド命名で 「これは更新」「これは参照」 が伝わるようにできる✍️✨
- 「読み取りのつもりが更新してた😱」事故を防げる🛡️💥
1) まずCQSってなに?(CQRSとの違いも軽く)🙂📚
CQS(Command Query Separation)って?
1つのメソッドは、どっちかにしようねってルールだよ😊
- Command:状態を変える(副作用あり)🧾➡️🗄️
- Query:情報を返すだけ(副作用なし)🔎➡️📋
“副作用”っていうのは、DB更新・配列更新・ファイル書き込み・外部API呼び出し・状態変更…みたいな「世界が変わる」やつね🌍⚡
CQRSとの違い(超ざっくり)🌱
- CQS:メソッド単位の「混ぜない」ルール
- CQRS:クラス/層/モデルまで「分けて作る」設計スタイル🧩✨
この章は CQS に集中だよ〜😊✨
2) Command / Query の見分け方👀✨(迷ったらこれ!)

まずは超シンプル判定✅
質問:そのメソッド、呼んだあと世界は変わる? 🌍
- 変わる → Command 🧾
- 変わらない → Query 🔎
ありがちな例(学食モバイル注文)🍙📱
| やりたいこと | 判定 | 理由 |
|---|---|---|
| 注文する(PlaceOrder) | Command 🧾 | 注文が増える(状態が変わる) |
| 支払う(PayOrder) | Command 💳 | ステータスが変わる |
| 注文一覧を見る(GetOrderList) | Query 🔎 | 表示するだけ |
| 売上集計を見る(GetSalesSummary) | Query 📊 | 計算して返すだけ |
3) 命名のコツ(ここが地味に超効く)✍️✨😆
Commandの命名✅(動詞で“やる”感じ)
placeOrderpayOrdercancelOrder
コツ: “Get”とか“Find”は使わない🙅♀️(更新なのに読み取りっぽくなる!)
Queryの命名✅(get / find / list / search など)
getOrderListfindOrderByIdsearchOrders
4) ハンズオン:同じクラス内で「混ぜない」整理🧹✨
ここでは第3〜4章の「ごちゃごちゃサービス」から、メソッド単位で分けてスッキリさせます😊🧼
4-1) ダメな例:読み取りが“こっそり更新”してる😱💥
よくある事故:一覧取得(Query)のつもりが、閲覧日時を更新してたとか…😭
// OrderService.ts(ダメな例😱)
type OrderStatus = "ORDERED" | "PAID";
type Order = {
id: string;
status: OrderStatus;
totalYen: number;
lastViewedAt?: Date; // ← これが事故ポイント💣
};
export class OrderService {
private orders: Order[] = [];
placeOrder(totalYen: number): string { // Commandっぽい✅
const id = crypto.randomUUID();
this.orders.push({ id, status: "ORDERED", totalYen });
return id;
}
getOrderList(): Order[] { // Queryのつもり…😇
// 😱😱😱 ここで更新してる!!!(副作用)
for (const o of this.orders) {
o.lastViewedAt = new Date();
}
return this.orders;
}
}
これ、何がヤバいかというと…👇😵💫
- 「一覧見ただけ」なのに 状態が変わる
- 将来DB保存になったら、GETしただけでUPDATE が走る可能性もある😱
- テストもしんどい(時間が絡むし、参照しただけで変わる)🧪💦
4-2) まずは“読み取りはDTOで返す”にする🎁✨
Queryの返り値は ドメインそのもの(Order) じゃなくて、 画面に必要な形(DTO) にして返すと事故が減るよ😊✨
// dto.ts
export type OrderListItemDto = {
id: string;
status: "ORDERED" | "PAID";
totalYen: number;
};
4-3) 改善:Queryは副作用ゼロ、Commandは更新だけ✅✨
// OrderService.ts(改善版✨)
type OrderStatus = "ORDERED" | "PAID";
type Order = {
id: string;
status: OrderStatus;
totalYen: number;
};
export type OrderListItemDto = {
id: string;
status: OrderStatus;
totalYen: number;
};
export class OrderService {
private orders: Order[] = [];
// ✅ Command:状態を変える(副作用OK)
placeOrder(totalYen: number): string {
const id = crypto.randomUUID();
this.orders.push({ id, status: "ORDERED", totalYen });
return id; // ※ID返すのはよくある実務スタイル👍
}
// ✅ Command:状態を変える(副作用OK)
payOrder(orderId: string): void {
const order = this.orders.find(o => o.id === orderId);
if (!order) throw new Error("注文が見つかりません");
if (order.status !== "ORDERED") throw new Error("未注文以外は支払えません");
order.status = "PAID";
}
// ✅ Query:読むだけ(副作用ゼロ)
getOrderList(): OrderListItemDto[] {
return this.orders.map(o => ({
id: o.id,
status: o.status,
totalYen: o.totalYen,
}));
}
}
ポイントはこれ!👇✨
- Queryは“読むだけ”(更新しない)🧼
- QueryはDTOで返す(中身を直接触らせない)🎁
- Commandは更新する(こっちは堂々と世界を変えてOK)🧾⚡
5) ミニ演習:それ、Command?Query?📝✨😆
次のメソッド名を見て、どっちか当ててみて〜!🎯
getTodaySalesSummary()updateMenuPrice(menuId, newPrice)findOrderById(orderId)createOrderAndReturnList()← これ地雷臭しない?😆💣
答え(こっそり)👇🙈
- 1 Query / 2 Command / 3 Query / 4 混ぜてる(CQS違反の匂いが濃い)
6) 「混ぜたくなる」瞬間の対処法🧯✨
✅ パターンA:Commandの結果、画面に返すものが欲しい😵💫
例:注文したら「注文内容」をすぐ表示したい!
おすすめ順👇
- CommandはIDだけ返す → そのあとQueryで取る(CQS的に最強)🏆
- Commandが “最小限の結果” を返す(例:
orderId、newStatus)🪶 - Commandでドメイン全部返す(最後の手段)😇
CQRSに進むと、だいたい「Commandの後にQuery」スタイルが気持ちよくなるよ😊✨
✅ パターンB:Queryでキャッシュ更新したい(副作用では?)🤔
キャッシュやメトリクス更新は「ビジネス状態じゃない」ことも多いけど、 初心者のうちは混乱しやすいから…
- まずはQueryに書き込みを入れない(安全第一🛡️)
- 必要になったら「Read側の仕組み」として切り出す(後の章でやるやつ!)📦✨
7) AI活用コーナー🤖💖(CQSはAIにめっちゃ相性いい!)
プロンプト例1:Command/Query判定をAIにやらせる👀
「このメソッドはCommand/Queryどっち?理由も。副作用があるなら指摘して」 って貼り付けるだけで、事故発見率が上がるよ〜✅✨
プロンプト例2:命名案を大量に出す✍️
「注文を確定する のCommand名を英語で10個、動詞始まりで」
「注文一覧を取得 のQuery名を get/find/list 系で10個」
プロンプト例3:CQS違反のにおい検知🚨
「このクラスをCQS観点でレビューして。混ざってるメソッドを列挙して、分割案も出して」
8) 章末チェックリスト✅✅✅(これ通ればOK!)
- Queryが DB/配列/オブジェクト を書き換えてない🧼
- Queryは DTOで返して、ドメインを直接返してない🎁
- Commandは やることが名前から伝わる(動詞スタート)✍️
- 「Commandの後にQueryで取り直す」流れがイメージできる🔁✨
9) 最新事情ちょこっと(2026年1月時点)🗓️✨
- TypeScriptの安定版は 5.9.3(npmの “latest”)だよ📦✨ (NPM)
- 5.9 のリリースノートには
--module node20みたいな Node向けオプションの話も出てきてて、モジュールまわりはどんどん整ってきてるよ〜🧩 (typescriptlang.org) - Nodeは v24 が Active LTS、v25 が Current という並び(2026-01-12/19更新)📌 (nodejs.org)
- 直近だと Node 24.13.0(LTS)のセキュリティリリースも出てるよ🛡️ (nodejs.org)
- さらに先の話だけど、TypeScriptは 6.0(ブリッジ)→7.0(ネイティブ移行) の流れが公式ブログで語られてるよ🚀 (Microsoft for Developers)
次章の予告🎬✨
第6章で「同じクラス内でも混ぜない」ができたら、次はいよいよ…
第7章:CQRSの最小形(CommandHandler / QueryService) 🧩✨ “クラスとして分ける”に進みます😊💕
必要なら、この第6章のハンズオンを あなたの第3〜4章のコード構成に寄せた形(ファイル分割・関数名の統一) に整えたバージョンでも書くよ〜!📁✨