Skip to main content

コマンド・クエリ分離原則(CQS) とは?

CQS(Command Query Separation)は、バートランド・メイヤーが提唱した設計原則で、メソッドを以下の2種類に分類します:
  • クエリ: オブジェクトの状態を取得するが、状態を変更しない
  • コマンド: オブジェクトの状態を変更するが、値を返さない
この原則により、メソッドの副作用が明確になり、コードの理解と保守が容易になります。

コマンド・クエリ責任分離(CQRS) とは?

CQRS(Command Query Responsibility Segregation:コマンド・クエリ責任分離)は、CQSの原則をシステムアーキテクチャレベルに拡張した設計パターンです。 単にメソッドを分離するだけでなく、読み取り(クエリ)と書き込み(コマンド)で異なるモデルやデータストアを使用することで、それぞれを最適化できます。

CQS から CQRS への進化

CQS(Command Query Separation)の基本

CQS はメソッドレベルでの分離原則で、各メソッドは「状態を変更する(コマンド)」か「値を返す(クエリ)」のどちらか一方のみを行います。

CQS の限界と課題

CQS は優れた原則ですが、システムが複雑になると以下の課題が生じます:
  1. パフォーマンスの問題
  1. スケーラビリティの制限
  • 読み取りと書き込みの負荷が異なる場合の対応が困難
  • 単一のモデルで両方の要求を満たすのは非効率
  1. ドメインモデルの複雑化
  • 表示用の要求とビジネスロジックが混在
  • モデルが肥大化しやすい

CQRS:CQS の課題を解決する進化形

CQRS は、これらの課題を解決するためにアーキテクチャレベルで読み取りと書き込みを分離します:
このように、CQRS は CQS の原則を発展させ、より大規模で複雑なシステムに対応できるパターンです。

なぜ コマンド・クエリ責任分離(CQRS) が重要なのか?

従来の設計では、状態を変更するメソッドが同時にデータを返す設計が多く、コードが複雑化しやすい問題がありました。CQRS を用いることで以下のメリットが得られます。
  • コマンドとクエリの責務が明確化され、保守性が向上する
  • データの変更処理をシンプルに保つことができる
  • クエリ側とコマンド側の拡張・最適化を別々に行える

解説

修正前のコード

問題点

  • 状態変更(deposit)とクエリ(balance の取得)が混ざっている
  • メソッドの責務が曖昧でテストや保守が難しくなっている

修正後のコード

解決された問題

  • コマンドとクエリが明確に分離され、責務が明確化された
  • テストが容易になり、変更に対して柔軟に対応できるようになった

より実践的な CQRS の例

ここまでの例は主に CQS レベルの分離でしたが、真の CQRS パターンでは読み取りと書き込みで異なるモデルを使用します。

アーキテクチャレベルでの CQRS 実装例

この例では:
  • 書き込み側は複雑なビジネスロジックとドメインモデルを扱う
  • 読み取り側は表示に最適化された非正規化データを扱う
  • イベントを通じて両者の整合性を保つ

サービスの種類について

アプリケーションサービス(UserCommandService)
  • ユースケースを実装する
  • トランザクション境界を管理
  • ドメインモデルを調整
  • 外部システムとの連携を制御
ドメインサービス
  • ドメインロジックのうち、特定のエンティティに属さないもの
  • 複数のエンティティにまたがる処理
  • ビジネスルールの実装

コマンドの戻り値についての考察

純粋な CQRS では、コマンドは値を返さないことが理想ですが、実際のアプリケーションでは何らかのフィードバックが必要な場合があります。

実践的なアプローチ

CQRS の適用における注意点とデメリット

いつ CQRS を使うべきか

適している場合:
  • 読み取りと書き込みの要求が大きく異なる
  • 高いスケーラビリティが必要
  • 複雑なドメインロジックがある
  • イベントソーシングと組み合わせて使用する
適さない場合:
  • シンプルな CRUD アプリケーション
  • 小規模なプロジェクト
  • チームに CQRS の経験がない

デメリットと課題

  1. 複雑性の増加
    • コードベースが大きくなる
    • 理解とメンテナンスが困難になる可能性
  2. 結果整合性
    • 読み取りモデルの更新に遅延が発生する可能性
    • 一時的にデータの不整合が見える場合がある
  3. 開発コスト
    • 初期実装に時間がかかる
    • テストが複雑になる

関連パターンとの組み合わせ

CQRS は以下のパターンと組み合わせることで、より強力になります:
  • Event Sourcing: すべての状態変更をイベントとして保存
  • Domain-Driven Design (DDD): 複雑なビジネスロジックの整理
  • Saga Pattern: 分散トランザクションの管理
  • Read/Write Database 分離: パフォーマンスの最適化

まとめ

CQRS を用いることで、システム内における状態変更と状態取得が明確に分離され、保守性・柔軟性が向上します。 ただし、すべてのアプリケーションに適しているわけではなく、複雑性とのトレードオフを慎重に検討する必要があります。 小規模なプロジェクトでは CQS レベルの分離から始め、必要に応じて本格的な CQRS へと発展させることをお勧めします。

練習問題 (1)

修正前のコード

修正後のコード

解説

元のコードでは、アイテム追加時に合計価格を返すという状態変更とクエリが混ざった設計でした。 CQRS を適用し、add_item メソッドは純粋なコマンド(状態変更のみ)に変更し、total_price メソッドを独立したクエリとして設計しました。これにより責務が明確になり、コードのテストと保守が容易になりました。

練習問題 (2)

修正前のコード

修正後のコード

解説

元のコードでは、email 変更の成功時に変更後の email を返却するという、状態変更と取得クエリを混ぜた設計でした。 CQRS を適用し、update_email を純粋なコマンドとして扱い、メールアドレス取得は current_email メソッドに分離しました。これにより、コードの意図が明確になりました。

練習問題 (3)

修正前のコード

修正後のコード

解説

元のコードでは、注文キャンセル処理とステータス取得が同じメソッドに混在していました。 CQRS を導入してキャンセル処理を純粋なコマンドとして分離し、ステータス取得を独立したクエリメソッドとして提供することで、責務を明確化できました。

練習問題 (4)

修正前のコード

修正後のコード

解説

元のコードでは在庫の削除処理と在庫数取得が混在していました。 CQRS を適用して、remove_stock という状態変更操作を純粋なコマンドにし、在庫数の取得をクエリとして別途提供しました。これにより、コードの役割が明確になりました。

練習問題 (5)

修正前のコード

修正後のコード

解説

元のコードでは、投稿の公開処理と公開日時取得というコマンドとクエリが混ざっていました。 CQRS に従い、publish メソッドを純粋なコマンドとして定義し、公開日時取得を別途 published_date メソッドで提供することで、処理の責務が明確になり、保守性が向上しました。