コマンド・クエリ分離原則(CQS) とは?
CQS(Command Query Separation)は、バートランド・メイヤーが提唱した設計原則で、メソッドを以下の2種類に分類します:- クエリ: オブジェクトの状態を取得するが、状態を変更しない
- コマンド: オブジェクトの状態を変更するが、値を返さない
コマンド・クエリ責任分離(CQRS) とは?
CQRS(Command Query Responsibility Segregation:コマンド・クエリ責任分離)は、CQSの原則をシステムアーキテクチャレベルに拡張した設計パターンです。 単にメソッドを分離するだけでなく、読み取り(クエリ)と書き込み(コマンド)で異なるモデルやデータストアを使用することで、それぞれを最適化できます。CQS から CQRS への進化
CQS(Command Query Separation)の基本
CQS はメソッドレベルでの分離原則で、各メソッドは「状態を変更する(コマンド)」か「値を返す(クエリ)」のどちらか一方のみを行います。CQS の限界と課題
CQS は優れた原則ですが、システムが複雑になると以下の課題が生じます:- パフォーマンスの問題
- スケーラビリティの制限
- 読み取りと書き込みの負荷が異なる場合の対応が困難
- 単一のモデルで両方の要求を満たすのは非効率
- ドメインモデルの複雑化
- 表示用の要求とビジネスロジックが混在
- モデルが肥大化しやすい
CQRS:CQS の課題を解決する進化形
CQRS は、これらの課題を解決するためにアーキテクチャレベルで読み取りと書き込みを分離します:なぜ コマンド・クエリ責任分離(CQRS) が重要なのか?
従来の設計では、状態を変更するメソッドが同時にデータを返す設計が多く、コードが複雑化しやすい問題がありました。CQRS を用いることで以下のメリットが得られます。- コマンドとクエリの責務が明確化され、保守性が向上する
- データの変更処理をシンプルに保つことができる
- クエリ側とコマンド側の拡張・最適化を別々に行える
解説
修正前のコード
問題点
- 状態変更(deposit)とクエリ(balance の取得)が混ざっている
- メソッドの責務が曖昧でテストや保守が難しくなっている
修正後のコード
解決された問題
- コマンドとクエリが明確に分離され、責務が明確化された
- テストが容易になり、変更に対して柔軟に対応できるようになった
より実践的な CQRS の例
ここまでの例は主に CQS レベルの分離でしたが、真の CQRS パターンでは読み取りと書き込みで異なるモデルを使用します。アーキテクチャレベルでの CQRS 実装例
- 書き込み側は複雑なビジネスロジックとドメインモデルを扱う
- 読み取り側は表示に最適化された非正規化データを扱う
- イベントを通じて両者の整合性を保つ
サービスの種類について
アプリケーションサービス(UserCommandService)- ユースケースを実装する
- トランザクション境界を管理
- ドメインモデルを調整
- 外部システムとの連携を制御
- ドメインロジックのうち、特定のエンティティに属さないもの
- 複数のエンティティにまたがる処理
- ビジネスルールの実装
コマンドの戻り値についての考察
純粋な CQRS では、コマンドは値を返さないことが理想ですが、実際のアプリケーションでは何らかのフィードバックが必要な場合があります。実践的なアプローチ
CQRS の適用における注意点とデメリット
いつ CQRS を使うべきか
適している場合:- 読み取りと書き込みの要求が大きく異なる
- 高いスケーラビリティが必要
- 複雑なドメインロジックがある
- イベントソーシングと組み合わせて使用する
- シンプルな CRUD アプリケーション
- 小規模なプロジェクト
- チームに CQRS の経験がない
デメリットと課題
-
複雑性の増加
- コードベースが大きくなる
- 理解とメンテナンスが困難になる可能性
-
結果整合性
- 読み取りモデルの更新に遅延が発生する可能性
- 一時的にデータの不整合が見える場合がある
-
開発コスト
- 初期実装に時間がかかる
- テストが複雑になる
関連パターンとの組み合わせ
CQRS は以下のパターンと組み合わせることで、より強力になります:- Event Sourcing: すべての状態変更をイベントとして保存
- Domain-Driven Design (DDD): 複雑なビジネスロジックの整理
- Saga Pattern: 分散トランザクションの管理
- Read/Write Database 分離: パフォーマンスの最適化