本文へスキップ
BecomeCoder

C#デザインパターンコース · 第3章 現代の設計パターン · レッスン24

CQRS ― コマンドとクエリの分離

ブラウザで完結

導入

「状態を変える操作(登録・更新)」と「状態を読むだけの操作(検索・集計)」は、性質がまったく違います。CQRS(Command Query Responsibility Segregation)は、この2つを別々の道として設計し、それぞれを最適化しやすくする考え方です。

図解

flowchart LR
    U["ユーザー"] -->|Command<br/>変更する| W["書き込みモデル"]
    U -->|Query<br/>読むだけ| R["読み取りモデル"]
    W --> S["状態"]
    S --> R
    style W fill:#fff3e0
    style R fill:#e8f5e9

サンプル

var store = new List<string>();
var commands = new TaskCommands(store);
var queries = new TaskQueries(store);

commands.Add("買い物");      // 変更する側(戻り値なし)
commands.Add("掃除");
Console.WriteLine(queries.Count());   // 2(読むだけの側)

// Command: 状態を変える(結果は返さない)
class TaskCommands
{
    private readonly List<string> _store;
    public TaskCommands(List<string> store) => _store = store;
    public void Add(string title) => _store.Add(title);
}
// Query: 状態を読む(変更しない)
class TaskQueries
{
    private readonly List<string> _store;
    public TaskQueries(List<string> store) => _store = store;
    public int Count() => _store.Count;
}
  • Command:状態を変える操作。原則として値を返さない
  • Query:状態を読む操作。副作用を持たない
  • 分けることで、読み取り側だけキャッシュする等、それぞれを最適化しやすい

やってみよう

TaskQueries に「先頭の1件を返す」First() を足してみましょう。読み取り専用のメソッドが状態を変えないことを意識できます。

演習

var store = new List<int>();

// TODO: store に値を足す Command クラス(Push メソッド)を定義してください
___

var cmd = new NumberCommands(store);
cmd.Push(10);
cmd.Push(20);
Console.WriteLine(store.Sum());   // 30
  • 期待される出力: 30
ヒント1を見る

class NumberCommands { private readonly List<int> _s; public NumberCommands(List<int> s) => _s = s; public void Push(int n) => _s.Add(n); }

ヒント2を見る

Commandは状態を変えるだけ。読み取り(Sum)は別(Query側)の責務です

まとめ

  • CQRSは「変更(Command)」と「参照(Query)」を別の道に分ける
  • Commandは値を返さず、Queryは副作用を持たない
  • 読み書きを別々に最適化・スケールできる

次回: null チェックを消す Null Object パターンです。

実際に動かしてみよう

本文のサンプルや演習のコードは、コードブロック右上の「コピー」ボタンでコピーして、下のエディタに貼り付ければそのまま実行できます。

C# — ブラウザ内で実行

ブラウザ内でC#を動かす環境を読み込みます(初回のみ数秒)。
スクロールして表示された時点でも自動で読み込まれます。