Command Query Responsibility Segregation (CQRS)
Command Query Responsibility Segregation (CQRS)とは?
Command Query Responsibility Segregation (CQRS) は、データを変更するアクション(コマンド)と、データを取得するアクション(クエリ)を分離する設計パターンです。両方に同じモデルを使用する代わりに、CQRS ではそれらを2つの異なる経路に分割します。コマンドとクエリを分離することで、システムは各タスクをより効率的に処理でき、アプリケーションの成長に伴って、パフォーマンスの向上、コードの明確化、スケーリングの容易化につながります。
CQRS の主要概念
コマンド
コマンドとは、システム内のデータを変更するアクションであり、データベースにおける書き込み操作などを指します。これは、何かを作成、更新、削除するなど、特定のタスクを実行するためのリクエストを表します。コマンドはデータを返しません。システムに「何かを実行せよ」と伝えるものです。
例: ユーザーがプロフィール情報を更新するとき、名前、メールアドレス、またはその他のシステム詳細を変更するために、“UpdateUserProfile” のようなコマンドが送信されます。
クエリ
クエリとは、データを変更せずに取得するためのリクエスト、つまりデータベースにおける読み取り操作です。クエリは、データの検索や表示など、情報を取得するために使用されます。コマンドとは異なり、クエリはデータを返すだけで、何も変更しません。
例: ユーザーが自分のプロフィール詳細を表示したい場合、“GetUserProfile” のようなクエリがシステムから関連情報を取得します。
CQRS はどのように機能するのか?
CQRS の仕組みを理解するための現実世界のたとえとして、高速道路に車とトラックのための別々の車線があると考えることができます。トラックは重い荷物(コマンド)を扱い、車は素早くスムーズに移動します(クエリ)。それぞれを専用の車線に保つことで、交通の流れが良くなり、両方のタスクがより効率的に行われます。
CQRS は、コマンド(データ変更)とクエリ(データ取得)の処理を明確に分離することで機能します。データの挿入や更新などのコマンドが発行されると、変更を処理するために設計された専用の経路を通ります。システムはこれらのコマンドを正確に処理し、多くの場合、関連するビジネスロジックやイベントをトリガーします。一方、クエリは高速かつ効率的なデータ読み取りに最適化された別の経路を通じて処理されます。
CQRS では、コマンドとクエリを分離することが、必ずしもそれぞれに異なるデータベースを使用する必要があることを意味するわけではない点に注意することが重要です。焦点は振る舞いと責任を分離することにあり、それは必要に応じて同じデータベース内で行うことも、複数のデータベースにまたがって行うこともできます。
図- CQRS 高レベルアーキテクチャ.png
図: CQRS 高レベルアーキテクチャ
例: ユーザー登録システム
ユーザーが登録すると、RegisterUserCommand というコマンドがトリガーされます。このコマンドは新しいユーザーを作成し、その詳細を検証し、データベースに追加します。裏側では、この変更を記録する `UserRegisteredEvent` のようなイベントが保存されます。
後で、システムの別の部分(またはユーザー)がユーザーの詳細を表示する必要がある場合、GetUserProfileQuery というクエリが使用されます。このクエリは、変更を加えることなく、データベースからユーザーのプロフィールデータを取得します。
CQRS と Event Sourcing
Event Sourcing は、CQRS と一緒によく使用される手法です。従来のシステムでは、データが変更されたとき(ユーザーがプロフィールを更新する場合など)、データの最終状態だけを保存します。たとえば、ユーザーがメールアドレスを変更した場合、データベースには新しいメールアドレスだけが保存され、古いメールアドレスは失われます。
Event Sourcing では、最終状態だけを保存するのではなく、すべての変更を個別のイベントとして保存します。そのため、ユーザープロフィールの更新の場合、“User created”、“Email updated”、“Profile picture changed” のようなイベントを保存します。各イベントは、何がいつ起こったかを記録します。これらのイベントは、システム内のすべての履歴ログのように、順番に保存されます。
これらのイベントはシステムの信頼できる情報源になります。コマンドが実行されると(例:ユーザーのプロフィールを更新する)、新しいイベントが作成され、このイベント一覧に追加されます。時間の経過とともに、これらすべてのイベントが蓄積され、変更の全履歴を表すようになります。
システムの現在の状態をクエリまたは表示する必要がある場合(ユーザープロフィールを取得するなど)、システムはこれらのイベントを正しい順序で再生し、現在の状態を再構築できます。たとえば、「User created」と「Email updated」というイベントを再生して、現在のメールアドレスとプロフィールの詳細を表示する場合があります。
CQRS と CQS の違い
CQRS (Command Query Responsibility Segregation) と CQS (Command Query Separation) はどちらも、コマンドとクエリの区別を強調する設計原則ですが、いくつかの点でわずかに異なります。
| 側面 | CQS (Command Query Separation) | CQRS (Command Query Responsibility Segregation) |
|---|---|---|
| 定義 | メソッドはコマンド(データを変更)またはクエリ(データを取得)のいずれかです。 | システムはコマンドとクエリを別々のモデルまたは経路に分離します。 |
| 複雑さ | メソッド内で適用されるシンプルな原則です。 | より複雑で、コマンドとクエリのために別々のモデルまたはレイヤーを伴います。 |
| ユースケース | コマンドとクエリが同じ構造内で処理される、より単純なシステムに適しています。 | 読み取りと書き込みを個別に最適化できる、大規模で複雑なシステムに最適です。 |
| データ処理 | コマンドとクエリの両方に同じデータモデルが使用されます。 | コマンドとクエリで異なるデータモデル、さらにはデータベースを使用できます。 |
| 焦点 | 主に、メソッドが単一の責任を持つという事実に焦点を当てます。 | システム全体にわたるスケーラビリティ、パフォーマンス、責任の分離に焦点を当てます。 |
| スケーラビリティ | 両方が1つのモデルで処理されるため、システムのスケーラビリティへの影響は小さいです。 | コマンドとクエリが独立して処理されるため、高いスケーラビリティがあります。 |
| 柔軟性 | 両方の操作が1つの構造に結び付いているため、柔軟性は限定的です。 | より高い柔軟性があります。コマンドとクエリに異なるデータベースやストレージ機構を使用できます。 |
| Event Sourcing | 通常は使用されません。 | 時間の経過に伴うすべての変更を追跡するために、イベントソーシングと併用されることがよくあります。 |
表: CQS と CQRS の違い
CQRS のユースケース
CQRS が有用な一般的なシナリオをいくつか示します。
高い読み取り/書き込みワークロード
読み取り操作と書き込み操作の数に大きな不均衡があるシナリオでは、CQRS が理想的です。たとえば、Eコマースプラットフォームでは、多数のユーザーが商品情報を問い合わせる(読み取り)一方で、カートを更新したり購入したりする(書き込み)ユーザーは少ない場合があります。CQRS により、システムはクエリ側を独立してスケールさせ、大量の読み取りリクエストを処理しながら、書き込み操作に影響を与えないようにできます。
複雑なビジネスロジック
データ変更(コマンド)にのみ適用される複雑なビジネスルールを扱う場合、CQRS はこの複雑さを分離することで役立ちます。たとえば、トランザクションが厳しく規制されている金融システムでは、コマンド側で厳格な検証と処理ルールを適用できます。一方、クエリ側はシンプルなままで、口座残高や取引履歴の取得に集中します。
イベントソーシングと監査証跡
すべての変更について完全な監査証跡を維持する必要があるシステムでは、CQRS はイベントソーシングと相性がよいです。たとえば、医療アプリケーションでは、患者の医療記録に対するすべての変更をイベントとして保存でき、システムは誰がいつ変更を行ったかを容易に追跡できる一方で、クエリは記録の現在の状態にすばやくアクセスできます。
リアルタイム分析
ソーシャルメディアプラットフォームなど、リアルタイム分析を必要とするアプリケーションでは、CQRS はユーザーデータへの継続的な更新(例:いいね、シェア)を処理しながら、リアルタイムの洞察(例:トレンドトピック)のためのクエリを処理できます。クエリ側は、コマンド側で進行中の更新に影響されることなく、速度を重視して最適化できます。
マイクロサービスアーキテクチャ
マイクロサービスベースのシステムでは、CQRS はコマンドとクエリを独立して処理するために異なるサービスを提供します。たとえば、物流会社では、あるサービスが注文処理(コマンド)を管理し、別のサービスが追跡とレポート作成(クエリ)を処理でき、システムの柔軟性と保守性が向上します。
CQRS をサポートするツールとフレームワーク
CQRS の実装を容易にするツールやフレームワークはいくつかあります。以下は、人気のある選択肢の簡単な概要です。
Axon Framework (Java) は、CQRS とイベントソーシングをサポートするフル機能のフレームワークです。コマンド、クエリ、イベントを処理するための組み込みコンポーネントを提供しており、複雑な Java アプリケーションで人気のある選択肢となっています。
MediatR は、.NET アプリケーション向けのもう一つの軽量ライブラリで、コマンドとクエリの分離を簡素化します。リクエストをクリーンで整理された方法でルーティングするのに役立ち、小規模から中規模のシステムにおける CQRS に最適です。
Laravel Command Bus は Laravel フレームワークの一部です。開発者はこのツールを使用して、コマンドとクエリを別々に定義します。CQRS を実装したい PHP ベースのアプリケーションにとって優れた選択肢です。
EventStore は、イベントソーシング向けに設計された専用データベースで、CQRS と組み合わせて使用されることがよくあります。単なるデータではなくイベントを保存するため、過去のイベントを簡単に再生してシステムの状態を再構築できます。
NServiceBus は、分散システム全体でコマンド、イベント、メッセージを処理することで CQRS と相性よく機能するメッセージングフレームワークです。CQRS を使用するマイクロサービスアーキテクチャに最適です。
CQRS の利点
スケーラビリティとパフォーマンスの向上
コマンドとクエリを分離することで、CQRS は各部分を独立してスケールできます。たとえば、アプリケーションが書き込み操作(コマンド)よりもはるかに多くの読み取り操作(クエリ)を処理する場合、コマンド側に影響を与えずにクエリ側をスケールでき、全体的なパフォーマンスが向上します。
より明確なコード構成
CQRS は関心の明確な分離を促進します。コマンドは変更の実行を扱い、クエリはデータ取得を処理します。これにより、各システム部分に明確な責任があるため、コードを理解しやすく、保守しやすくなります。
複雑なビジネスルールのより適切な処理
CQRS を使用すると、データ更新に関連する複雑なビジネスルールはコマンド側で処理でき、データの読み取りにはより単純で効率的なクエリを使用できます。この分割により、システムに過負荷をかけたりコードを複雑にしすぎたりすることなく、入り組んだロジックを管理しやすくなります。
読み取りデータと書き込みデータにおけるデータベース選択の柔軟性
CQRSでは、コマンドとクエリに異なるデータベースを使用するという自由な選択ができます。たとえば、高速な読み取り(クエリ)には高度に最適化されたNoSQLデータベースを使用し、書き込み(コマンド)にはリレーショナルデータベースを使用できます。この柔軟性により、各タスクに最適なツールを選択できます。
CQRSの課題と考慮事項
複雑性の増加
CQRSでは、2つの別々のモデルを管理する必要があります。1つはコマンド(書き込み)を処理するためのモデル、もう1つはクエリ(読み取り)を処理するためのモデルです。両方のモデルを維持および更新する必要があるため、コードベースの複雑性が増し、システムの開発と保守に必要な労力が増加します。
同期の課題
コマンド側とクエリ側を同期させ続けるのは難しい場合があります。コマンドを通じてデータが書き込まれると、その変更がクエリ側に反映されるまでに時間がかかることがあり、潜在的な遅延につながります。システム全体で正確なデータを確保するには、この同期を管理することが重要です。
CQRSが過剰になる場合の判断
CQRSは、単純なCRUD(Create, Read, Update, Delete)アプリケーションには必要ありません。データ要件が単純なシステムでは、CQRSによって追加される複雑性に見合う価値がない場合があります。CQRSは、大量の読み取りと書き込み、または複雑なビジネスロジックを持つ複雑なシステムに最も適しています。
コスト
CQRSを実装するには、多くの場合、コマンドとクエリ用に別々のデータベースを維持するなど、追加のインフラストラクチャが必要になります。これにより、特に両側をスケールする場合、ハードウェアとクラウドサービスのコストが増加する可能性があります。これらのタスクを処理するために複数のテクノロジーが必要になることで、費用が増大する可能性があります。
CQRSがベクトルデータベースとビッグデータで果たす役割
Milvusのようなベクトルデータベースでは、CQRSはさまざまな種類の操作を処理するための構造化された方法を提供します。
コマンド: これには、機械学習モデルによって生成されたベクトル埋め込みなど、大量のベクトルを挿入または更新するタスクが含まれます。Milvusでは、コマンドがデータベースの状態を更新します。
クエリ: これらの操作は、類似性検索を使用して大規模なデータセット内の類似アイテムを見つけるなど、ベクトルを取得します。Milvusのクエリは、特定の条件に一致する高次元ベクトルを返すことができ、レコメンデーションシステムや検索拡張生成(RAG)などのリアルタイム分析やAIアプリケーションでよく使用されます。
例: リアルタイム分析クエリとベクトルの挿入/更新の処理
Zilliz Cloud(Milvusのマネージド版)上でAI搭載のレコメンデーションシステムを運用しているとします。新しいユーザーデータや製品ベクトルが生成されると、システムは新しい埋め込みでベクトルデータベースを更新するためのコマンドを発行します。これらのコマンドにより、最新のベクトルがMilvusに挿入または更新されます。
同時に、レコメンデーションに最も類似したベクトルを取得するために、クエリを使用してリアルタイム分析が実行されます。クエリ側は、コマンド側で絶えず行われるベクトル挿入の影響を受けずに、関連データを迅速に見つけられるように最適化されています。
結論
CQRSは、コマンド(データ変更)とクエリ(データ取得)を分離することで、複雑なシステムを管理する強力な方法を提供します。この分離により、特に複雑なビジネスロジックを持つ大規模アプリケーションにおいて、スケーラビリティ、パフォーマンス、コードの明確性が向上します。CQRSはいくらかの複雑性を追加しますが、リアルタイム分析、高い読み取り/書き込みワークロード、イベントソーシングを扱うシステムなど、適切なシナリオで使用すると大きな利点をもたらします。ただし、より単純なアプリケーションでは、追加の複雑性は必要ない場合があります。結論として、CQRSは適切に適用されることで、システムの効率性と柔軟性を高めるための価値あるツールです。
CQRSのFAQ
- CQRSを使用する主な利点は何ですか?
CQRSは、データの更新(コマンド)とデータの取得(クエリ)の責務を分離することで、システムのスケーラビリティ、パフォーマンス、コードの明確性を向上させます。各側を独立して最適化できるため、高トラフィックまたは複雑なシステムに役立ちます。
- CQRSの使用を避けるべきなのはどのような場合ですか?
CQRSは、読み取りと書き込みを別々に扱う必要がない単純なCRUDアプリケーションでは、不要な複雑さを加える可能性があります。アプリケーションに大きなパフォーマンス要件や複雑なビジネスロジックがない場合、CQRSは過剰な設計になる可能性があります。
- CQRSではコマンドとクエリに別々のデータベースを使用する必要がありますか?
いいえ、コマンドとクエリに別々のデータベースを使用する必要はありません。CQRSで異なるデータベースを使用することはできますが、重要なのはコード内で振る舞いと責務を分離することです。アプリケーションのニーズに合っていれば、単一のデータベースを使用することもできます。
- CQRSはイベントソーシングとどのように連携しますか?
イベントソーシングでは、システムへのすべての変更がイベントとして保存されます。CQRSは、イベントを生成するコマンドを処理し、それらのイベントを再生してシステムの状態を再構築するためにクエリを使用することで、これを補完します。そのため、変更の完全な履歴が維持され、監査やデバッグが容易になります。
- CQRSはリアルタイム分析アプリケーションのパフォーマンスを向上させられますか?
はい、CQRSはリアルタイム分析に役立ちます。システムは継続的な更新(コマンド)をリアルタイムデータクエリとは別に処理でき、それぞれの目的に合わせて最適化し、一方が他方の速度を低下させるのを防ぎます。
- CQRSを実装する際の課題は何ですか?
主な課題には、複雑さの増加、コマンドモデルとクエリモデル間の同期管理、結果整合性の問題への対応、そして別々のパスやデータベースを維持することによるインフラコストの増加の可能性が含まれます。


