導入
「状態を変える操作(登録・更新)」と「状態を読むだけの操作(検索・集計)」は、性質がまったく違います。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 パターンです。