CQRSパターンフロー
このドキュメントでは、MBC CQRS ServerlessにおけるCQRS(Command Query Responsibility Segregation)パターンの実装について説明します。
CQRS概要
コマンドフロー - 書き込みパス
書き込み操作のフローです。
コマンドフローのステップ
- リクエスト受信: クライアントがPOST/PUT/DELETEリクエストを送信
- DTOバリデーション: コントローラーがclass-validatorを使用して入力を検証
- コマンドディスパッチ: コントローラーがコマンドを作成してディスパッチ
- ビジネスロジック: コマンドハンドラーがビジネスルールを実行
- 永続化: コマンドサービスが楽観的ロックでDynamoDBに永続化
- イベント発行: ドメインイベントがDynamoDB StreamsとDataSyncHandlerを経由してSNSに非同期で発行される
- レスポンス: 成功レスポンスをクライアントに返却
クエリフロー - 読み取りパス
読み取り操作のフローです。
クエリフローのステップ
- リクエスト受信: クライアントがGETリクエストを送信
- クエリディスパッチ: コントローラーがクエリを作成してディスパッチ
- データ取得: クエリハンドラーがデータサービスを呼び出す
- データベースクエリ: データサービスがDynamoDBまたはRDSにクエリ
- レスポンス: データをクライアントに返却
Read-Your-Writes整合性
結果整合性の課題
publishAsync はコマンドテーブルに書き込んだ直後に返却されます。SNS経由でトリガーされるプロジェクターがリードストアを更新する前に、後続の読み取りが古いデータを返す短い時間窓が存在します:
publishAsync()
│
▼
CommandTable ──► SNS ──► Lambda ──► ReadStore
│ ▲
│ ~async window~ │
└──── publishAsync returns ─── ─ │
│
Client reads here ───────────────────────┘ ← may return OLD data
これは結果整合性システムとして期待される動作ですが、ユーザーがレコードを作成・更新してすぐに一覧画面に遷移した際に、更新前の状態が表示されるという混乱を招くことがあります。
Read-Your-Writes (RYW) ソリューション
MBC CQRS Serverless v1.2.0 では、書き込みを行ったユーザーに対してこの非同期ウィンドウを埋めるセッションベースの Read-Your-Writes レイヤーが導入されました:
publishAsync()
│
├──► CommandTable ──► SNS ──► Lambda ──► ReadStore
│
└──► SessionTable ← TTL付きの小さなエントリ
│
▼
Repository.getItem / listItemsByPk / listItems
│
├── ReadStoreから取得(DataService)
└── SessionTableから保留中のコマンドを取得
│
└── マージ → 整合性のある結果を返す
RYW_SESSION_TTL_MINUTES が設定されている場合、publishAsync / publishPartialUpdateAsync の呼び出しごとに SessionService が専用のセッションテーブルに短命なエントリを書き込みます。Repository クラス(DataService をラップ)は自動的にそれらのエントリを読み取り、保留中のコマンドをクエリ結果にマージします。プロジェクターが実行される前でも、呼び出し元は自分の書き込みを即座に確認できます。