読者です 読者をやめる 読者になる 読者になる

ほげほげ(仮)

仮死状態

CQRSのメモ

設計

CQRSって知らなかったので調べました。かなり雑なメモです。

参考リンクとかではEvent Sourcingについても絡んできますが、ここでは触れてないです。

参考

ここにある記事を読みました。英語のとこはなんとくで読んだので怪しいですが。

ぼくのこのメモを読むより、下記の記事を読んだほうがいいです。

CQS(Command Query Separation)

CQRS(コマンドクエリ責務分離)に入る前に、CQS(コマンドクエリ分離原則)について。

CQSはコマンドとクエリを分けようってことです。

  • クエリ: 結果のみを返し、状態の変更は行わない
  • コマンド: 状態の変更のみを行い、結果は返さない

これをオブジェクトレベルで適用します。メソッドで分ける感じです。

ただ、Javaiteratorのようにnextで結果の取り出しと内部状態の変更をやっているものもあります。こういうイディオムは便利なので原則を破ることもあるようです。

CQRS(Command and Query Responsibility Segregation)

CQSではオブジェクトレベルで適用する感じでしたが、CQRSではもっと広い範囲に適用します。Event Sourcingも含むとアプリケーション全体になるかと思います。

CQRSではCQSと異なり、コマンドとクエリをメソッドで分けるのではなくオブジェクトごと分けます。

例えばこういうオブジェクトがあった場合、

CustomerService
void MakeCustomerPreferred(CustomerId)
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)

次のように分離します。

CustomerWriteService
void MakeCustomerPreferred(CustomerId)
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)

CustomerReadService
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()

クエリ側

データを取得するメソッドを持つだけで、各画面にマッチするDTOを生成して返却します。

多くのドメインではクエリ最適化が困難、インピーダンスミスマッチ問題などがあります。

CQRSを適用すれば、クエリ側はドメインを使いません。新しい概念の「Thin Read Layer」でデータベースから直接読み込んでDTOに反映します。

コマンド側

読み込みをドメインから分離すれば、コマンドの処理だけに焦点があたります。

リポジトリではGetByIdを除くとクエリはわずかになります。

クエリを分離したドメインモデルで作業をすれば、概念的なオーバヘッドが減り、コスト削減につながります。

(ここらへん怪しい)CQRSを適用すると、クエリとコマンドで同一データモデルを使うのかという問題がある。ここから先はEvent Sourcingの話っぽい?(ココのP23の下の部分)

まとめ

CQSはすぐにでも適用できそうですが、CQRSになると簡単にはいかなさそうです。

Event Sourcingも絡んでくると、ぼくはお手上げ状態になりそうです。

ここの最後にも書いてあるのですが、CQRSについてはだいぶ慎重になったほうが良さそうです。