arch-cqrs について
arch-cqrsは、コマンド処理とクエリモデルを分けるアーキテクチャスキルです。分離が有効な状況や、プロジェクションと結果整合性の扱いをチームが検討できます。
主な機能
- 01コマンドモデルとクエリモデルの分離
- 02イベントによる読み取りモデルの更新
- 03共有・個別ストレージの比較
- 04結果整合性の評価
- 05CQRSのアンチパターンの特定
活用シーン
- 01読み取りと書き込みの負荷差への対応
- 02ダッシュボード、検索、詳細画面の提供
- 03トランザクション書き込みとクエリストアの併用
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill arch-cqrsインストール後、Claude Code で/arch-cqrsを入力して読み込まれたか確認します。
詳細
1 行でまとめると
コマンド(状態を変更する操作)に使うモデルと、クエリ(読み取り専用のビュー)に使うモデルを分離します。それぞれを独立して進化、拡張、永続化できます。
このスキルを使う場合
- 読み込み負荷と書き込み負荷に大きな偏りがある場合 — 書き込み 1 回あたりのクエリが多い、またはその逆の場合。
- 同じデータでも、読み取りの用途によって必要な形が異なる場合(ダッシュボード、検索、詳細ページなど) — 1 つの標準的な読み取りモデルでは妥協が生じます。
- 自然な書き込みモデル(たとえば、不変条件が多い集約)ではビューのクエリが難しい場合。
- 書き込み側ではイベントソーシングが有効でも、読み取り側では高速な SQL クエリが必要な場合 — CQRS が橋渡しになります。
このスキルを使わない場合
- 読み込みと書き込みの量がほぼ同じで、必要なデータの形も同じ場合 — CQRS は単なるオーバーヘッドです。
- チームが結果整合性に不慣れな場合 — CQRS は設計上、結果整合性を導入します。
- システムが小規模で、通常のリポジトリパターンで十分な場合 — 過剰設計です。
- 明確な理由もなく、同じデータを格納するデータベーステーブルを 2 つ用意することを正当化するために「CQRS」を使っている場合 — それは単なるデータの重複です。
主な内容
1 つの基盤となる真実に対して、2 つのモデルを使います。
- 書き込みモデル — コマンド(
PlaceOrder、CancelShipment)を受け取ります。検証し、不変条件を適用して、永続化します。多くの場合、集約です(ddd-aggregate)。整合性を重視して最適化されており、読み取りには最適化されていません。 - 読み取りモデル — クエリを受け取ります。特定の利用者に合わせた形の DTO を返します。非正規化したり、事前に結合したり、キャッシュしたり、検索用にインデックス化したりできます。不変条件は適用されず、プロジェクションとして機能します。
同期の仕組み。 書き込みによってイベントが生成されます(多くの場合、ドメインイベントです。ddd-domain-event を参照)。読み取りモデルはプロジェクションです。ハンドラーがイベントを受け取り、マテリアライズドビューを更新します。読み取り側は常に書き込み側よりわずかに遅れます。これが結果整合性です。意識して受け入れましょう。
ストレージの種類。
- 同じデータベースに 2 つのモデルを置く。 最もシンプルな方法です。書き込みモデルと読み取りモデルは、同じデータベース内の異なるテーブル(または異なるスキーマ)です。トリガー、スケジュール済みジョブ、またはインプロセスのプロジェクションによって読み取りモデルにデータを反映します。
- 異なるデータベースを使う。 トランザクションストア(集約には Postgres)に書き込み、クエリストア(検索には Elasticsearch、高頻度の参照には Redis、ダッシュボードには別の Postgres スキーマ)にプロジェクションします。運用の複雑さが実際に増すため、読み取り要件が明確な場合に限って採用してください。
CQRS にイベントソーシングは必須ではありません。 両者はよく組み合わせて使われますが、独立したものです。コミット時にイベントを発行する通常の CRUD 書き込みモデルでも CQRS を実現できます。イベントが真実の源泉となるケースについては、arch-event-sourcing を参照してください。
結果整合性についての話し合い。 読み取りモデルには遅延が生じます。ドメインの専門家が「ユーザーはダッシュボードに変更がすぐに表示されることを期待している」と言う場合、プロジェクションの遅延を縮めるか、「変更を処理中です」と UI で伝えるかを選ぶ必要があります。このずれが存在しないふりをしてはいけません。
判断の目安
- 読み込みと書き込みに測定可能な偏りがある場合、または書き込みと読み取りで必要なデータの形が実質的に異なる場合に使います。「CQRS は高度そうだから」という理由で採用してはいけません。
- まずは単一データベースの CQRS(1 つの Postgres 内に 2 つのモデル)を基本とします。読み取りストアの特性(検索、地理空間、インメモリ)が書き込みストアと本当に異なる場合に限り、異なるデータベースを使います。
- イベントの下流の利用者がいる場合に限り、イベント駆動と組み合わせます。そうでなければ、よりシンプルなプロジェクションで十分です。
- 読み取りモデルは真実の源泉ではなく、プロジェクションです。読み取りモデル経由で読み取ってから書き戻していることに気づいたら、CQRS の原則を破っています。
アンチパターン
- モデルが1つだけの「CQRS」。
commands/とqueries/という名前の2つのフォルダーがあっても、分離せずに同じテーブルを読み書きしています。見せかけにすぎません。 - 読み取りモデルが書き込みも行うようになる。 コードが書き込みモデルを経由せず、読み取りストアに直接書き込みます。2つのモデルは気づかれないまま乖離していきます。
- 結果整合性をUIに伝えない。 ユーザーが変更を加えても、UIには古いデータが表示され、ユーザーは変更に失敗したと思ってしまいます。遅延について伝えてください。
- 負荷の大きい読み取りモデルのマイグレーション。 スキーマを変更するたびに全体を再投影する必要があり、時間がかかるうえにリスクも伴います。読み取りモデルをバージョン管理し、並行してマイグレーションを実行してください。
- 書き込みと読み取りが対称なまま(非対称性がない)のに採用する。 メリットがなく、オーバーヘッドだけが増えます。
関連項目
arch-event-driven— イベントは通常、書き込みを読み取りモデルのプロジェクションに伝播します。arch-event-sourcing— 書き込み側でイベントを信頼できる唯一の情報源として保存する場合、CQRSは自然な読み取り側のパターンになります。ddd-aggregate— CQRSの書き込みモデルの標準的な形です。ddd-repository— 読み取り側のデータ取得は通常、ドメインリポジトリ経由ではなく、別個のクエリーオブジェクトを使います。
参考資料
- Khononov著、Learning DDD、第8章(CQRS)。
- Greg Young著、「CQRS Documents by Greg Young」— CQRSの原初の提唱。
- Vaughn Vernon著、Implementing Domain-Driven Design — 第4章にCQRSの解説があります。
コメント
Markdown 対応