kg-icon
CQRSについて整理してみた
2025-02-24

はじめに

個人開発の参照系が結構複雑で集約も結構な数があり、レポジトリパターンだときついなーと感じていた矢先に、CQRSというパターンがあったなとふと思い出したので調べました。

現在進行形で導入中ですが知識だけでも備忘録としてまとめておきます。

CQRSパターンとは

CQRSパターンは、コマンド(書き込み)とクエリ(読み取り)の責務を分離するアーキテクチャパターンです。

データの変更(コマンド)と取得(クエリ)を別々のモデルやサービスで管理することで、システムのスケーラビリティ、パフォーマンス、可読性、変更容易性等を向上させることができます。

CQRSは大規模システムや高トラフィックなシステムで使われているイメージが多いと思いますが(自分だけの認識かもしれませんが)、小規模なアプリケーションでも全然使えます。

CQRSの特徴

以下が挙げられます。

  • 責務の分離
  • スケーラビリティの向上
  • イベントソーシングとの組み合わせ

責務の分離

読み取り処理と書き込み処理を明確に分離するのでそれぞれの役割を把握しやすくなります。

特に読み取りに関しては、集約単位でレポジトリパターンを使ってゴニョゴニョ頑張って取得しなくて済みます。

レポジトリパターンだと、集約が増えてくるとレポジトリがどんどん複雑かつ大きくなり、集約全体を分割取得する負荷が無視できなくなってきます。

これはパフォーマンスに直接悪影響を与えてボトルネックになりかねませんが、このデメリットを綺麗に片付けてくれます。

スケーラビリティの向上

読み取りと書き込みを独立してスケールさせることができます。

基本的に大半のサービスは読み取りの方が処理負荷が高いので、コマンドとクエリをDB単位で分ける場合は、読み取りDBのスペックは高くして書き込みDBはそこそこ、のような感じのスペック調整ができますね。

イベントソーシングとの組み合わせ

イベントソーシングはデータの状態を直接更新するのではなく、すべての変更を「イベント」として記録します。そして、記録したイベントを元に状態を構築するアプローチです。

例えば、「ユーザーが名前を変更した」というイベントを記録し、その履歴を適用することで現在の状態を再現します。

すべての変更がイベントとして保存されるため、監査ログや変更履歴の追跡が容易になったりしますが、設計や実装がやや複雑になる傾向がありますね。

後述しますがイベントソーシングの採用は必須ではないです。

CQRSでよくある誤解

自分もずっと疑問に思っていたことでしたが、以下はCQRSと組み合わせやすいというだけで一緒に採用する必要はないです。オプションですね。

  • イベントソーシング
  • DBの分離

イベントソーシング

CQRSの話でイベントソーシングがセットで出てくることが多いですが、CQRSを採用するからといってイベントソーシングを採用する必要はないです。

「将来的にマイクロサービスへの移行」を強く検討していたり「非機能要件的にサービス間の結合度をできるだけ下げなければならない」等があったりする場合は採用もアリかもしれませんが必須ではないです。

DBの分離

これもイベントソーシングの場合と同じで、CQRSを採用するからといって書き込みDBと読み取りDBを分ける必要はないです。

DBを各モデルで共有して使ったとしても、コマンドとクエリで明確に責務を分けてサービス運用しているならば、それはれっきとしたCQRSです。

コマンドとクエリのレイヤー管理

基本的には以下。

コマンドはドメインモデルの整合性を保つ必要があるので、ユースケースを通じてドメインモデルを操作します。

  • コマンド
    • プレゼンテーション → ユースケース → ドメインモデル → レポジトリ

クエリはデータ取得に特化するため、専用のクエリサービスを設け、ドメインモデルを介しません。取得したクエリサービス専用の返り値をそのまま返します。

  • クエリ
    • プレゼンテーション → ユースケース → クエリサービス
      • クエリサービスはドメインモデルではなくクエリサービス専用型のデータを返す

さいごに

個人的には、DB分離やイベントソーシングは採用しないにしても、CQRSはかなりのユースケースで使えると思います。

全ユースケースでCQRSパターンを採用する必要性はなく、複雑な読み取り処理だけCQRSパターンを採用するといった運用でも問題ないので。

実際に導入してみて、より深いPros & Consを実感できればなと思います。