🙆‍♀️

CQRS 完全に理解した

2022/07/08に公開

はじめに

  • CQRS を何となく理解するための記事です。
  • DDD の文脈から来ている概念ですが、この記事では DDD について深く触れません。DDD の知識がなくてもわかるように書いた(つもり)です。

CQRS

ざっと理解

  • Command and Query Responsibility Segregation

  • コマンドクエリ責務分離原則

  • Command(Write) と Query(Read) のモデルを分離するパターン

  • Commands: Change the state of a system but do not return a value.
    Queries: Return a result and do not change the observable state of the system (are free of side effects).
    (引用) https://martinfowler.com/bliki/CommandQuerySeparation.html

    Commands: システムの状態を変更するが、値を返さない(Void)
    Queries: 結果を返すが、システムの観測可能な状態を変化させない(副作用がない)

  • Greg Young 氏が提唱。ドキュメントはこちら
    CQRS_monolith_DB

データモデルから見る CQRS の意義

商品の注文を例にして考えます。
モデルはこんな感じです。
注文

「〇月△日に注文した商品の価格を調べる」というようなユースケースを考えてみましょう。
すると、以下のような操作が必要になるかと思います。

  • 注文日から注文・注文明細を取得する
  • 注文明細に紐づく商品を取得する

ここで、ビジネス上意味のあるまとまり単位(※)でデータを扱うことを考えるといくつか問題が発生します。
※ DDD でいう集約に当たります。以降集約という言葉を使います。

  1. 1注文ごとにN個の商品の情報を取得しなければならない(俗に言う N + 1 問題)
  2. 支払い方法、お届け先など不必要な情報まで取得してしまい、必要な情報をフィルタリングするのにコストがかかる
  3. 商品の価格順や名前順のソートが難しい

一方で、注文を登録したり、商品を登録したりする際は通常集約の単位で行います。
つまり、Command と Query で必要となるデータモデルを分けたほうが効率が良いわけです。

  • Command
    • 集約やそれらに関連するオブジェクト
  • Query
    • ユースケースごとに最適化されたオブジェクト。しばしば DTO と呼ばれる。

非機能要件から見る CQRS の意義

例えば以下のような要件に合致する場合、CQRS が有用です。

  • データの整合性
    • 読み取りでは特に強い整合性は求めていない(結果整合を受け入れられる)
  • トランザクション数の割合
    • 全体に占める読みこみの割合が非常に多く、急激なアクセス増加も考えられるが、それに耐えうる状態にしたい

ここで先ほど出てきた CQRS の図を見てみましょう。
CQRS_monolith_DB
このようなアーキテクチャであると、Write と Read のデータソースが同じであるため、

全体に占める読みこみの割合が非常に多く、急激なアクセス増加も考えられるが、それに耐えうる状態にしたい

という要件の対案としてスケールアップ(スペックアップ)という戦略しか取れなくなります。
そこで、Write と Read のデータソースを分けることを考えます。
CQRS_separated_DB
Write / Read それぞれの要件でデータソースを選択できますので、例えば Read 側は スケールアウトする(データソースを増やす)という戦略をとれます。
...と言うのはまだ早計で、大きな問題がまだあります。

Write と Read の 同期

Write と Read のデータソースを分離すると何らかの方法でデータの同期をとらないといけません。
Write 側でデータの更新や削除があったとしましょう。
Read 側への同期は複雑になることが想定されます。
マテリアライズドビューや AWS RDS のリードレプリカ DB を使用できるならばその辺りの複雑さは考慮しなくて良くなりますが、Read の要件に合わせたデータソースを選択できるという大きなメリットを潰してしまいます。
そこで、Event Sourcing の登場です。

Discussion