『CQRS Documents by Greg Young』を読んだ
CQRSに興味があったのでGreg Young氏のCQRS Documentsを読んでみた。
CQRSに興味を持ったきっかけ#
ドメイン層のメンテナビリティの重要性を感じる中で、ドメイン層は書き込み系の振る舞いだけを担い、読み取り系の責務は別のモジュールに移譲してしまった方がドメイン層をシンプルに保てて良いのではないかと考えるようになった。1
それってCQRSってやつじゃね?と思ったのでまずは原典に近いドキュメントを読んでみることにした。2
ここからはCQRS Documentsの内容をまとめていく。
“A Stereotypical Architecture”: 典型的なアーキテクチャ#
“A Stereotypical Architecture"として、典型的なアーキテクチャが紹介されている。CQRS Documentsではこのアーキテクチャに対して段階的にCQRSを導入していく形で説明が進んでいく。
CQRS Documents by Greg Young P2より引用
このアーキテクチャではシステムはCRUDのみの機能を持ち、クライアントとは常にDTOを介して通信する。
おそらくクライアントがUpdateUserNameのようなエンドポイントに更新後のユーザーネームだけを渡すのではなく/userに更新後のDTOを丸ごと渡す、というようなことだと思われる。
サーバー側では受け取ったDTOをドメインオブジェクトに詰め替えてバリデーションを行い、永続化する。
このような設計ではアプリケーションサービスの責務はDTOとドメインオブジェクトの詰め替えに終始し、ドメイン層はバリデーションを主な責務として受け持つ。データの更新方法はクライアントに委ねられてしまうため、ドメイン知識の一部がクライアント側に漏れ出してしまう。
このようなアーキテクチャでは簡潔性によってオンボーディングコストが低くなるメリットがある代わりに、次のような課題が存在する。
- スケーリング: 一般的なシステムでは読み取り操作は書き込み操作よりも2桁以上多い。読み取り操作から見ると非正規化されたデータ構造が適しているが、書き込み操作から見ると正規化されたデータ構造が適している。これらを単一のDBで扱おうとすると垂直スケーリングが必要になるが、垂直スケーリングには非常に高額なコストがかかる。
- DDD適用の困難さ: この設計ではAPIがデータ指向のインターフェースを持つため、システム全体がCRUDの4つの動詞に縛られる。(データの更新方法がクライアントまたはユーザー側に漏れ出ておりドメイン知識をソフトウェアで表現することが難しいため)
ここまでの感想#
さすがにこういったアーキテクチャのシステムは見たことがないので極端な例では…?とは思いつつ時代や場所によってはこういった設計のシステムも存在するのかもしれない。
CQRSとは#
CQRSはBertrand Meyerが提唱した『Command and Query Separation Principle』にその起源を持つ。Wikipediaではこの原則を次のように説明している。
It states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both.
とあり、すべてのメソッドはCommandかQueryのいずれかであるべきであり、両方を兼ねるべきではないと説明されている。
これに対し、Martin Fowlerは以下のようにPopのような操作はCommandでありQueryでもあり、必ずしも上記の原則を厳密に守らなくてもいいのではないか、ということを述べている。
Meyer likes to use command-query separation absolutely, but there are exceptions. Popping a stack is a good example of a modifier that modifies state. Meyer correctly says that you can avoid having this method, but it is a useful idiom. So I prefer to follow this principle when I can, but I’m prepared to break it to get my pop. (Fowler)
CQRS Documents by Greg Young P17より引用
ここまでの感想#
実際にWebサービスを開発していてもたとえばユーザー作成メソッドが作成したユーザーのIDを返すような処理は普通に書くし、自分もどちらかというと定義ほどは厳密に運用しなくても十分恩恵を受けられるのではないかという立場。
“A Stereotypical Architecture"にCQRSを導入する#
“A Stereotypical Architecture"として最初に紹介されたアーキテクチャでは、ドメインモデルがCommandとQueryの両方に使用されていた。このアプリケーションにCQRSを適用するとQuery側とCommand側はそれぞれ次のようになる。
Query#
Query処理側にはデータ取得のためのメソッドのみが含まれる。
元のアーキテクチャではドメインモデルを生成し、それをDTOにマッピングしたうえでクライアントに返却していた。多くの場合でドメインモデルとDTOは異なるモデルであるため、以下のような課題があるがこれがCQRSの適用によって解決される。
- 複数集約から1つのDTOを生成する場合、複数回DBにアクセスする必要がありパフォーマンスが低下する。また、集約の境界が曖昧になる。
- ドメイン層にQueryの責務が多く含まれている(e.g. repositoryのinterfaceにQuery系のメソッドが多く含まれ、ページングやソート情報が含まれている)
After CQRS has been applied there is a natural boundary. Separate paths have been made explicit. It makes a lot of sense now to not use the domain to project DTOs. Instead it is possible to introduce a new way of projecting DTOs.
CQRS Documents by Greg Young P20より引用
とあるように、CQRSを適用することでCommandとQueryの処理経路が明確に分離される。この段階ではドメインモデルをDTOの生成に使用しない方が合理的である。
その代わりに"Thin Read Layer"という方法でDTOを生成することができる。
CQRS Documents by Greg Young P21より引用
この層はDBから直接データを読み取り(ドメインモデルを迂回し)DTOを生成する。
こうすることで、ドメイン層からQueryの責務を切り離すことができ、ドメイン層の純度を上げることができる。
Command#
CQRSを適用するとCommand側のアーキテクチャは以下のようになる。
CQRS Documents by Greg Young P22より引用
元のアーキテクチャでは書き込み系の処理をする際もDTOをクライアントから受け取っていたが、CQRS適用後はデータ中心ではなく振る舞い中心の契約を採用している点と読み取り処理が分離されている点が大きな違い。(元のアーキテクチャでは書き込み時にクライアントからDTOを渡していたが、ここではメッセージが渡されるようになっている)
また、元のアーキテクチャでドメイン層に存在していた次のような課題が解決されている。
- リポジトリに大量の読み取りメソッドが存在する
- DTO構築のためにドメインオブジェクトの内部状態を公開するゲッターメソッドが存在する
- DTO構築のために複数の集約オブジェクトをそれぞれ読み込むことで、非効率なクエリが実行される
ドメインイベント、イベントソーシング#
本文にはドメインイベント、イベントソーシングについても記載があり読んだがここでは割愛。
ここまでの感想#
ドメイン層からQuery系の責務を切り離すことでメンテナビリティが向上する、というのが自分が(直近)CQRSに興味を持っていた一番の理由だったのでそうだよなーと思いながら読んだ。
また(書いてあるとおりだが)読み取り単位を必ずしもドメインモデル単位にする必要がないためパフォーマンスの最適化が行いやすい点も大きなメリットだといえそう。
結局CommandとQueryはまったく別の責務なことが多いので両者を分離することでメリットを得られるケースは多そう。
作業習慣の違い#
CQRS Documentsを読むまでは意識していなかったが、CQRSを採用することで
- ドメイン層
- リードモデル
- クライアント
の3つの要素に分解して作業を進めることができるため、プロジェクトに関わる開発者数をより効果的に増やすことができる。(サーバーとクライアントの作業分担をできるのはCQRSかどうかにかかわらないが、重要なのは1つのサーバーアプリケーションを2並列で安全に進めやすいという点だろう。)
また、開発者には次のような観点の差異が存在する。
- 技術的熟練度
- ビジネスドメインに関する知識
- コスト
- ソフトスキル
ドメイン層の実装には4つすべての項目が秀でた人材が最適である一方、リードモデルの実装にはそれらの開発者要件は必ずしも当てはまるとは限らない。
ここまでの感想#
開発効率の観点は持ってなかったが、たしかにCQRSを採用することで(1つのアプリケーションが2つのモジュールに分離されるので)コンフリクトせずに開発を進めやすそう。(モジュール設計はプロジェクトによるので厳密には2つのモジュールとは限らないが)
感想#
事前のイメージ通り、CQRSを適用することで開発効率を高められそうなイメージが持てた。
CQRS Documents内でも触れられていたが、結局CQRSにしろイベントソーシングにしろ一定のオーバーヘッドはあるので、システムやビジネスの課題や要件、今後の見通しを元に必要性を検討するのが(いつだって)重要そう。
というのはありつつ、ドメイン層のメンテナビリティを高めるためにCQRSを採用するのはかなり有効な選択肢の1つだとも感じているので、ひとまず個人のプロジェクトで検証してみたいと思う。
-
もちろんアプリケーションの状況にもよるが…。repositoryのRead系メソッドの返り値がドメインモデルと一致しないケースが多くなるような場合では特にCQRSのメリットが効果を発揮しやすいような気がしている。状況にかかわらずCQRSをやったほうがいいのかどうかはまだあまりわかっていない。ちなみにここでのCQRSは基本的には同一DBでCのアプリケーションコードとQのアプリケーションコードが分離されている、くらいの方法をざっくり想定して書いている。(CとQでDBを分けるパターンは流石にtoo muchなケースが多そうなので) ↩︎
-
厳密にこのドキュメントが原典であることを確認したわけではない。正確な情報をお持ちの方がいたら教えて下さい。 ↩︎