GoF以外のプログラミング・デザインパターン #20 CQRS(Command Query Responsibility Segregation)

2025/12/11

プログラミング 学習

t f B! P L
eyecatch CQRS(Command Query Responsibility Segregation)パターンは、Sagaパターンに続いて、分散システム設計のもう一つの柱です。 「コマンド(命令)とクエリ(問い合わせ)の責務を分離する」というのが特徴ですね。 簡単に言うと、データベースのクエリを効率化することですね。 「書き込み」と「読み込み」を同じクラスで管理するのではなく、それぞれ別の処理で効率化します。

CQRSの基本概念

操作種別 目的 モデル 特徴
Command データを変更(登録・更新・削除) 書き込みモデル 整合性・トランザクション重視
Query データを参照(検索・一覧表示) 読み取りモデル 高速・キャッシュ・集計向き

構成イメージ

Command API ↓ Write Model(DB) ← トランザクション整合性重視 ↓ イベント発行(Event Sourcing) Read Model(DB) ← 検索・集計用に最適化 ↓ Query API

サンプルコード

// Write Model(書き込み) class UserCommandService { constructor(eventBus) { this.eventBus = eventBus; this.users = new Map(); } createUser(id, name) { this.users.set(id, { id, name }); this.eventBus.publish({ type: "UserCreated", data: { id, name } }); } } // Read Model(読み込み) class UserQueryService { constructor() { this.userList = []; } handle(event) { if (event.type === "UserCreated") { this.userList.push(event.data); } } getAllUsers() { return this.userList; } } // Event Bus(イベント伝達) class EventBus { constructor() { this.subscribers = []; } subscribe(handler) { this.subscribers.push(handler); } publish(event) { this.subscribers.forEach(h => h.handle(event)); } }

実行

const bus = new EventBus(); const queryService = new UserQueryService(); bus.subscribe(queryService); const commandService = new UserCommandService(bus); commandService.createUser(1, "Alice"); commandService.createUser(2, "Bob"); console.log("Read Model:", queryService.getAllUsers());

出力

Read Model: [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]

Event Sourcingとの関係

CQRSは、よく"Event Sourcing"と組み合わせて使われます。 まず、Commandの結果を「イベント」として発行します。 そのイベントをReplayすることで、Read Modelを再構築可能になります。 つまり、「状態を保存する」代わりに「状態の変化(イベント)」を保存すると言う流れです。

メリット

・読み取りと書き込みのスケール戦略を分けられる ・読み取り側をキャッシュ・検索エンジン(Elasticsearchなど)で最適化可能 ・書き込み側でドメインルール(DDD)を厳密に保てる

デメリット

・モデルが2つになるため、設計と同期が複雑 ・イベント伝達の遅延により最終的整合性(Eventually Consistent)になる ・小規模アプリではオーバーエンジニアリングになりやすい

補足

CQRSはDDD(ドメイン駆動設計)と強く結びついており、よく以下のパターンと併用されます。
- Event Sourcing(履歴で状態を再現) - Saga(分散トランザクション管理) - Outbox Pattern(イベント配信の整合性確保)

あとがき

読み込みと書き込みをそれぞれクラス分けして、管理と効率化をしやすいようにしたデジザインパターンですが、 おそらく、プログラマーにとって、好き嫌いが発生すると思います。 個人的には、単に分けるだけだと、効率が悪いので、これらをまとめる親クラスを作って管理する方がいいという感覚でした。 もちろん、モデルクラスも分けた方がいいのではないか?と言う、1段階上のパターンも思いつきますよね。 でも、ネガティブな印象はなく、効率的になるのであれば、覚えておくべきデザインパターンだと思います。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ