CQRS(コマンドクエリ責務分離)とは?仕組み・Read Model/Write Model・Query Serviceと実装パターンを解説

CQRS(Command Query Responsibility Segregation/コマンドクエリ責務分離)は、データを更新するコマンド側と、データを参照するクエリ側を、モデルもコードも分けて設計するアーキテクチャパターンです。1つのモデルで更新と参照の両方をまかなうと、複雑なドメインでは「整合性を厳密に守りたい更新の要求」と「画面ごとに都合のよい形で速く読みたい参照の要求」が衝突します。CQRSはこの2つを切り離し、更新系はビジネスルールの整合性に、参照系は画面が必要とする形のデータ提供に専念させます。

なお、SQLにおける「クエリ」という言葉そのものの意味や書き方を調べている場合、対象はCQRSではありません。その場合はクエリとは?SQLとの違い・種類・実行の仕組みを参照してください。本記事はアーキテクチャパターンとしてのCQRSに絞って解説します。

まとめ

  • CQRSは、更新用モデル(Write Model)と参照用モデル(Read Model)を分離するパターン。基になった考え方はBertrand MeyerのCQS(コマンドクエリ分離)で、それをサービス単位に広げたものです。
  • 参照系はQuery Serviceが担い、複数の集約を跨いで画面用のDTOを直接組み立てます。ドメインモデルやリポジトリを経由しません。
  • イベントソーシングと相性は良いものの、CQRSにイベントソーシングは必須ではありません。まずモデル分離だけで始められます。
  • Read ModelとWrite Modelを別データベースに分けると、更新が参照に反映されるまで遅延が生じる結果整合性を受け入れる必要があります。
  • 全システム向きではありません。単純なCRUDで足りる領域には過剰で、参照と更新の要求・負荷が大きく食い違う複雑なドメインで効きます。

以下では、CQRSの定義と各構成要素の役割、採用判断までを順に見ていきます。

CQRSの定義と設計思想

CQRSの中心的な主張は「更新(コマンド)の責務と参照(クエリ)の責務を、別々のモデルに分ける」ことです。従来の設計では、注文なら Order という1つのモデルが、注文の登録・キャンセルといった更新も、一覧表示や検索といった参照も兼ねます。ドメインが複雑になると、このモデルは更新のためのバリデーションやビジネスルールと、参照のための表示項目・集計項目の両方を抱え込み、肥大化します。

CQRSはここで、更新を扱うWrite Modelと、参照を扱うRead Modelを別物として設計します。両者は同じデータを別の視点で表現するため、更新系はドメインの不変条件を守ることだけを、参照系は画面に必要なデータを返すことだけを考えればよくなります。責務が1方向に絞られるので、片方の都合でもう片方が複雑化しなくなる、というのが最大の狙いです。

CQS(コマンドクエリ分離原則)との違い

CQRSの土台はBertrand Meyerが提唱したCQS(Command Query Separation/コマンドクエリ分離)です。CQSはEiffel言語や著書「オブジェクト指向入門」で示された原則で、1つのメソッドは「状態を変えるが値を返さないコマンド」か「値を返すが状態を変えないクエリ」のどちらか一方であるべき、と定めます。これはメソッド1個のレベルの規律です。

CQRSは、この分離をメソッドから一段引き上げ、オブジェクトやサービスのレベルに適用したものです。提唱したのはGreg Youngで、2010年前後にこの名称が広まりました。Martin Fowlerも自身のbliki(martinfowler.com/bliki/CQRS.html)で解説しています。CQSが「1メソッドの中で混ぜない」なら、CQRSは「更新用と参照用でモデルそのものを2つに割る」という関係になります。

Write ModelとRead Modelの役割分担

CQRSの具体像は、Write ModelとRead Modelがそれぞれ何を担うかを見ると掴めます。

Write Model(更新系)の設計

Write Modelは、コマンド(登録・変更・削除の要求)を受けてドメインの状態を変える側です。ここでは集約単位で不変条件を守ることが最優先になります。たとえば「在庫を下回る注文は受け付けない」といったルールの検証は、すべて更新系に集約します。Write Modelは参照の都合(一覧表示に必要な結合結果など)を持ち込まないので、ドメインの本質だけを表現できます。ドメイン駆動設計と組み合わせる場合、この更新系がエンティティ・値オブジェクト・集約の設計対象になります。設計手法の詳しい背景はドメイン駆動設計(DDD)とは?意味・基本用語・実装と採用判断をわかりやすく解説で扱っています。

Read Model(参照系)の設計

Read Modelは、画面や外部APIが必要とする形にデータを整えて返す側です。更新系の集約構造をそのまま返すのではなく、表示に必要な項目だけをまとめたビューや、あらかじめ結合・集計しておいた読み取り専用のテーブルを持つことが多くなります。ポイントは、Read Modelを更新系の制約から自由にすることです。正規化された更新系とは別に、参照系は非正規化して読み取り速度を優先する、といった判断ができます。CQRSで理解の要になるのがこのRead Modelで、更新モデルと同じデータを「読むために都合のよい形」で二重に持つ点が要点です。

Query Serviceによる参照系の実装

参照系を具体的に実装する仕組みがQuery Serviceです。更新系ではリポジトリを通じて集約を取得しますが、参照系で同じことをすると問題が起きます。1画面の表示に複数の集約が必要な場合、集約ごとにリポジトリで取り出して結合すると、無駄な読み込みとコードの複雑化を招くためです。

Query Serviceは、この参照専用の入口です。ドメインモデルやリポジトリを経由せず、複数の集約に跨るデータを1回の問い合わせでDTO(表示用のデータ構造)として直接組み立てます。インターフェースは「この画面に必要なデータを返す」という参照の意図を名前で表し、戻り値はドメインオブジェクトではなくDTOにするのが基本です。こうすることで、参照の都合が更新系のドメインモデルに逆流しなくなります。参照系の性能が問題になる箇所は、まずこのQuery Serviceの問い合わせを見直すのが定石です。

イベントソーシングとCQRSの関係

CQRSはイベントソーシングとセットで語られがちですが、両者は別の概念です。イベントソーシングは、状態そのものではなく「状態を変えた出来事(イベント)の列」を記録し、そこから現在の状態を再構築する手法です。イベントストアの選択肢や再生・版管理の実装は、イベントソーシングの仕組みと採用判断を実装視点で解説した記事で扱っています。

CQRSと相性が良い理由は、更新系がイベントを発行し、そのイベントを使って参照系のRead Modelを更新する、という流れが自然に作れるからです。更新系は出来事を書き込むことに、参照系はその出来事を受けて読み取り用データを組み立てることに専念できます。ただし、CQRSにイベントソーシングは必須ではありません。まずはWrite/Read Modelの分離だけを導入し、必要になってからイベントソーシングを足すこともできます。両者を本格的に組み合わせる実装フレームワークとしてはAxon Frameworkが代表例で、Axon Frameworkの概要:CQRSとイベントソーシングの基盤となるフレームワークの特徴と利点で具体的な構成を解説しています。

CQRS導入の利点と結果整合性のコスト

CQRSの利点は、責務の分離から生まれます。更新系と参照系が独立するため、それぞれを別々に最適化できます。参照が重いシステムでは、Read Model用のデータベースだけをスケールさせたり、キャッシュや検索エンジンに置き換えたりでき、更新系に影響を与えません。モデルが単純化し、複雑なドメインでも更新ロジックと表示ロジックが混ざらないため、保守しやすくなります。

一方でデメリットは明確です。第一に、モデルもコードも二重になるので、実装量と学習コストが増えます。第二に、Read ModelとWrite Modelを物理的に分けると、更新が参照へ反映されるまでにわずかな遅延が生じます。これが結果整合性で、「登録した直後の一覧にまだ出てこない」といった挙動を許容する設計が必要になります。強い一貫性を全画面で求める要件とは相性が悪く、この遅延を業務として受け入れられるかが採否の分かれ目になります。

CQRSを採用すべき場面・すべきでない場面

CQRSは「とりあえず入れておく」パターンではありません。分離のコストに見合う理由がある場合にだけ採用します。

採用が効くのは、更新と参照の要求が大きく食い違う領域です。具体的には、複雑なビジネスルールを持つ更新系と、多様な切り口で高速に読みたい参照系が同居し、参照の負荷が更新を上回るようなケースです。集計・分析ダッシュボード、EC の注文管理、在庫や金融取引のように、書き込みは厳密で読み取りは多彩、という要件で力を発揮します。マイクロサービスでサービスごとに読み書きの負荷特性が異なる場合にも向きます。

逆に、採用すべきでないのは単純なCRUDで完結する領域です。マスタ管理や社内向けの小規模な業務画面のように、更新と参照が同じ形で足りるシステムにCQRSを持ち込むと、二重のモデルと結果整合性の複雑さだけが残り、得るものがありません。「複雑さに立ち向かうための道具」であって、単純な領域ではむしろ有害です。まずは通常の3層構造で作り、参照系の性能や更新系の複雑さが実際にボトルネックになった部分にだけ、後から局所的にCQRSを適用するのが安全な進め方です。

CQRSの実装パターン(DDD・リポジトリとの関係)

実装は、更新系と参照系のインターフェースを分けることから始めます。同じ Order に対して、更新用のサービスと参照用のサービスを別々に定義します。

// 更新系:コマンドを受け、集約の不変条件を守りながら状態を変える
interface OrderCommandService {
    void placeOrder(PlaceOrderCommand command);
    void cancelOrder(OrderId id);
}

// 参照系:画面に必要なDTOを直接返す(ドメインモデル・リポジトリを経由しない)
interface OrderQueryService {
    OrderSummaryDto findSummary(OrderId id);
    List<OrderListItemDto> search(OrderSearchCondition condition);
}

更新系の OrderCommandService はリポジトリを介して集約を取得・保存し、ビジネスルールを適用します。参照系の OrderQueryService はリポジトリを使わず、必要なデータを直接問い合わせてDTOを返します。ここでリポジトリを更新系だけに限定する(参照系はリポジトリを持たない)のがCQRSの実装上の勘所で、リポジトリで参照系まで賄おうとすると分離の意味が薄れます。データベースは、最初は更新系・参照系で同一のものを共有し、負荷が問題になった段階でRead Model専用のデータストアへ分離する、という段階的な進め方が現実的です。ドメイン駆動設計と組み合わせる場合、更新系の集約設計とCQRSの参照分離は補完関係になります。

よくある質問

CQRSの「クエリ」とSQLのクエリは同じ意味ですか?

別物です。SQLのクエリはデータベースへの問い合わせ文そのものを指しますが、CQRSの「クエリ」は「状態を変えずにデータを参照する責務」という設計上の役割を指します。SQLのクエリの意味や書き方を知りたい場合はクエリとは?SQLとの違い・種類・実行の仕組みを参照してください。

CQRSとCQSはどう違いますか?

CQSはBertrand Meyerが示した、1つのメソッドをコマンドかクエリのどちらかに限定する原則です。CQRSはその考え方をメソッドからモデル・サービスの単位へ広げ、更新用と参照用のモデルを2つに分けたパターンです。CQSが土台、CQRSがその応用という関係です。

CQRSにイベントソーシングは必須ですか?

必須ではありません。CQRSはWrite ModelとRead Modelの分離を指すだけで、イベントソーシングを使わずリレーショナルデータベースだけでも実装できます。両者は相性が良いため一緒に語られますが、独立して導入できます。

結果整合性とは何ですか?

更新した内容が参照側に反映されるまで、わずかな時間差が生じる状態を許容する考え方です。Read ModelとWrite Modelを別データベースに分けると発生します。「登録直後の一覧にまだ表示されない」ケースが起こり得るため、その遅延を業務上許せるかを事前に確認します。

小規模なシステムにもCQRSを使うべきですか?

単純なCRUDで足りる小規模システムには不向きです。モデルの二重化と結果整合性の複雑さがコストに見合いません。参照系の性能や更新系の複雑さが実際に問題化した箇所にだけ、後から部分的に適用するのが現実的です。

関連記事

資料請求

RELATED POSTS 関連記事