CQRSとは、データを変更する処理(Command)と、データを参照する処理(Query)を分けて設計する考え方です。
一言でいうと
CQRSは「書き込みに都合のよいモデル」と「読み取りに都合のよいモデル」を分ける設計です。
単純なCRUDでは、更新も参照も同じモデルで扱います。CQRSでは、更新側と参照側で責務を分けます。
CommandとQuery
| 種類 | 役割 | 例 |
|---|---|---|
| Command | 状態を変更する | 注文を作成する、支払いを確定する |
| Query | 状態を参照する | 注文一覧を見る、売上集計を見る |
Commandは成功/失敗やIDを返すことはありますが、複雑な画面表示用データを返す責務は持ちません。Queryは状態を変えません。
なぜ分けるのか
更新と参照では、求められる形が違うことがあります。
| 観点 | 更新側 | 参照側 |
|---|---|---|
| 目的 | ルールを守って状態を変える | 速く分かりやすく表示する |
| モデル | 業務ルール中心 | 画面や検索中心 |
| 整合性 | 厳密さが重要 | 遅延を許容できる場合がある |
| 最適化 | トランザクション | インデックス、キャッシュ、集計 |
たとえば注文作成では在庫や支払いのルールが重要ですが、注文一覧では検索や表示速度が重要です。
基本構成
| 部品 | 役割 |
|---|---|
| Command Handler | 更新処理を実行する |
| Domain Model | 更新時の業務ルールを守る |
| Write Store | 正の状態を保存する |
| Read Model | 参照に最適化したデータ |
| Query Handler | 参照要求に応答する |
Read Modelは、画面に合わせたテーブル、検索インデックス、キャッシュなどで表現できます。
処理の流れ
注文作成後に注文一覧へ反映する例です。
| 順番 | 処理 |
|---|---|
| 1 | CreateOrder Commandを受け取る |
| 2 | Command Handlerが業務ルールを実行する |
| 3 | Write Storeへ注文を保存する |
| 4 | イベントや同期処理でRead Modelを更新する |
| 5 | 注文一覧QueryはRead Modelを読む |
Read Modelの更新を非同期にすると、短い時間だけ参照側が古い状態を返すことがあります。
Event Sourcingとの違い
CQRSとEvent Sourcingは一緒に語られますが、別の考え方です。
| 用語 | 意味 |
|---|---|
| CQRS | 更新と参照を分ける |
| Event Sourcing | 状態そのものではなくイベント列を保存する |
CQRSだけなら、通常のDBに現在状態を保存しても構いません。Event Sourcingを必ず導入する必要はありません。
向いている場面
| 向いている | 向いていない |
|---|---|
| 参照が非常に多い | 単純なCRUD |
| 表示用の集計が重い | 小規模な管理画面 |
| 更新ルールが複雑 | 一覧と詳細だけのアプリ |
| 読み取りを別DBや検索基盤に最適化したい | 整合性遅延を許容できない |
CQRSは強力ですが、複雑さも増えます。最初から入れるより、更新と参照の要求が明確に分かれてから検討するのが現実的です。
よくある誤解
CQRSは必ずマイクロサービスにする
同じアプリ内でCommand HandlerとQuery Handlerを分けるだけでもCQRSです。サービス分割は別の判断です。
Read Modelは常に非同期でよい
同期更新で十分な場合もあります。非同期にする場合は、いつ反映されるか、古いデータをどう見せるかを設計します。
すべての機能にCQRSを使う
単純なCRUDに使うと、Handlerやモデルが増えて複雑になります。複雑な部分だけに適用するのが実務的です。
まとめ
CQRSは、更新と参照の要求が大きく違うときに効果を発揮します。
重要なのは、書き込みモデルを業務ルールに寄せ、読み取りモデルを表示や検索に寄せることです。ただし、整合性遅延と実装複雑性を受け入れられるかを先に判断します。