CQRS
書き込み(コマンド)と読み取り(クエリ)のモデルを分離し、それぞれを独立に最適化する設計。
概要
CQRS(Command Query Responsibility Segregation、コマンドクエリ責務分離)は、データを「書き込む(コマンド)」処理と「読み取る(クエリ)」処理を、別々のモデルとして分離する設計パターンです。多くのシステムは1つのデータモデルで読み書き両方をこなしますが、CQRS はそこをあえて2つに割り、書き込み用の構造と読み取り用の構造をそれぞれ独立に最適化します。
ふつうのCRUD的な設計では、同じテーブル・同じオブジェクトに対して、更新もするし一覧表示のための検索もします。ところが「正しく整合を保って書きたい」という都合と、「速く・都合のよい形で読みたい」という都合はしばしば衝突します。CQRS は、その2つの要求を1つのモデルに背負わせるのをやめ、書き込み側は業務ルールと整合性に集中し、読み取り側は表示や検索のしやすさに集中させる、という分業を持ち込みます。
なぜ生まれたか
伝統的なアプリケーションは、1つのドメインモデル(データの構造とふるまいをまとめたもの)で読み書きの両方をこなしてきました。小規模なうちはこれで十分ですが、システムが育つと読み書きの要求がちぐはぐになってきます。書き込みは「在庫を二重に引き当てない」といった不変条件を厳密に守る必要がある一方、読み取りは「商品一覧を、店舗名やカテゴリと結合して、並べ替えて高速に返す」ことを求めます。
この2つを1つのモデル・1つのテーブル構成で満たそうとすると、無理が出ます。正規化(正規化)して整合を守りやすくすると、読み取りのたびに多数の結合が必要になって遅くなる。逆に読み取りを速くするために非正規化すると、今度は書き込み時の整合維持が難しくなる。読み書きは負荷の量も性質もまったく違うのに、同じモデルに縛られているせいで、片方を最適化するともう片方が犠牲になるのです。
CQRS は、この綱引きの原因が「読みと書きを同じモデルで扱っていること」だと見抜き、思い切って両者を切り離しました。書き込み側と読み取り側を別モデルにすれば、それぞれを独立に、しかも別々にスケール(スケーリング)させて最適化できます。読み取りが圧倒的に多いサービスなら、読み取り側だけを増強する、といったことも自然にできるようになります。
詳細
コマンド側とクエリ側を分ける
CQRS では、アプリケーションへの操作を2種類に分けて考えます。コマンドは「注文を確定する」「住所を変更する」のように状態を変える操作で、結果として何かを返すことは基本的に期待しません。クエリは「注文履歴を表示する」のように状態を読むだけの操作で、何も変えません。この「状態を変えるか、読むだけか」で処理経路そのものを分けるのが CQRS の骨格です。
次の図は、書き込みと読み取りを別のモデル・別のデータストアに分け、両者を結果整合性でつなぐ典型的な構成です。
図のように、クライアントの操作はコマンドとクエリに振り分けられ、それぞれ専用のモデルとデータストアへ向かいます。書き込みが起きると、その結果は非同期にリードモデルへ反映されます。読み取り側は「表示したい形そのまま」にデータを持てるので、結合や整形をせずに高速に返せます。
イベントソーシングとの組み合わせ
CQRS が最も自然に噛み合うのがイベントソーシングとの組み合わせです。イベントソーシングは状態変化をイベント列として追記しますが、そのイベント列は「何が起きたか」の記録には理想的でも、一覧検索には向きません。ここで CQRS の出番です。コマンド側はイベントをイベントストアに追記するだけに徹し、そのイベントを購読して、検索・表示に最適化したリードモデル(投影=プロジェクション)を組み立てておく — こうすると、書き込みは追記専用で高速、読み取りは専用の形で高速、という両取りが成立します。
ただし両者は必ずセットというわけではありません。CQRS は「読み書きのモデルを分ける」という考え方であって、その裏側がイベントソーシングである必要はなく、ふつうのRDBMSを2系統に分けるだけの軽い CQRS も十分に成立します。逆にイベントソーシングを採るなら、読み取りのために CQRS はほぼ必須になります。この非対称な関係を押さえておくと、両者を混同せずに済みます。
トレードオフと使いどころ
CQRS の代償は複雑さです。モデルもデータストアも2系統に増え、書き込みを読み取り側へ反映する同期の仕組みが必要になります。多くの場合この同期は非同期で行われるため、「書いた直後に読むと、まだ反映されていない」という結果整合性の性質が表に出ます。ユーザーが更新ボタンを押した直後に古い一覧が見える、といった挙動を UI や仕様として許容できるかどうかが、採否の分かれ目になります。
したがって CQRS は、あらゆる場面で使うべき万能パターンではありません。読み取りと書き込みの負荷や要求が大きく食い違う領域、複雑な業務ルールを持つドメイン、あるいはイベントソーシングを採用する部分にこそ効きます。単純な CRUD にまで持ち込むと、得られる恩恵よりも二重管理のコストのほうが上回ります。システム全体に一律で適用するのではなく、恩恵の大きい一部の境界(サブドメイン)にだけ選択的に導入する、というのが実務での定石です。