イベントソーシング
状態の変更をイベントとして記録し、イベントの再生で現在の状態を復元する設計パターン
イベントソーシングとは
イベントソーシングは、状態の変更をイベントとして記録し、イベントの再生 (リプレイ) で現在の状態を復元する設計パターンである。CRUD の「更新」ではなく「追記」でデータを管理する。
CRUD vs イベントソーシング
CRUD とイベントソーシングの違いを図で示す。
CRUD:
UPDATE accounts SET balance = 900 WHERE id = 'A';
→ 以前の値 (1000) は失われる
イベントソーシング:
Event 1: AccountCreated { id: 'A', balance: 1000 }
Event 2: MoneyWithdrawn { id: 'A', amount: 100 }
→ 現在の状態: balance = 1000 - 100 = 900
→ 全履歴が残る
DynamoDB でのイベントストア
DynamoDB でのイベントストアのコード例を示す。
// イベントの書き込み (entityId = パーティションキー / version = ソートキー)
await db.put({
TableName: 'events',
Item: {
entityId: 'order-123',
version: 1, // 集約ごとの連番。並び順の根拠はこれ
occurredAt: Date.now(), // 記録用。並び順には使わない
eventType: 'OrderCreated',
data: { items: ['item-1'], total: 1000 },
},
// 同じ (entityId, version) が既にあれば失敗する = 追記の楽観的排他
ConditionExpression: 'attribute_not_exists(entityId)',
});
// 状態の復元 (イベントのリプレイ)
const events = await db.query({
TableName: 'events',
KeyConditionExpression: 'entityId = :id',
ExpressionAttributeValues: { ':id': 'order-123' },
ScanIndexForward: true, // ソートキー (version) 昇順 = 発生順
});
const state = events.Items.reduce((order, event) => {
switch (event.eventType) {
case 'OrderCreated': return { ...event.data, status: 'created' };
case 'OrderPaid': return { ...order, status: 'paid' };
case 'OrderShipped': return { ...order, status: 'shipped' };
default: return order;
}
}, {});
メリット
全変更履歴がイベントとして残るため、完全な監査証跡が得られる。任意の時点の状態を復元する「時間旅行」が可能で、デバッグ時に問題の再現が容易になる。イベントが発生するたびに下流システムへ自然に通知できるため、イベント駆動アーキテクチャとの親和性が高い。
デメリット
代表的なデメリットと、その定石の対策を以下にまとめる。
| デメリット | 対策 |
|---|---|
| クエリが複雑 | CQRS で読み取り用ビューを分離 |
| イベント数の増加 | スナップショットで高速化 |
| スキーマの進化 | イベントのバージョニング |
表に載りにくい最大の落とし穴は、リプレイと外部システムの相性である。Martin Fowler は原典で、イベント再生時に外部システムへ更新メッセージを送ると、外部システム側は本番処理と再生を区別できないため処理が壊れると指摘している。原典の対処は、外部システムをゲートウェイで包み、リプレイ中はゲートウェイを無効化しておくことである。無効化はドメインロジックから見えないようにする (PaymentGateway.send の呼び方はリプレイ中も変えない)。
逆向きの問題もある。外部システムへの問い合わせ結果がイベント処理の結果を左右する場合、12 月 5 日のイベントを 12 月 20 日に再生すると、必要なのは 12 月 5 日時点の為替レートで、再生時点のレートではない。過去日付を指定して取得できる外部システムなら任せられるが、そうでなければ問い合わせて得た値をイベント自身に記録し、再生では外部へ問い合わせない設計にしておく。この見極めを設計時に飛ばすと、「いつでも再生できる」という前提が後から崩れる。
CQRS との組み合わせ
CQRS との組み合わせを図で示す。
書き込み: イベントストア (DynamoDB)
↓ DynamoDB Streams
読み取り: マテリアライズドビュー (別テーブル or OpenSearch)
ユースケース
代表的なユースケースを以下にまとめる。
| ユースケース | 理由 |
|---|---|
| 金融取引 | 監査証跡が必須 |
| 注文管理 | 状態遷移の履歴が重要 |
| ゲーム | リプレイ機能 |
| コラボレーション | 変更の競合解決 |
全体像を把握するには関連書籍も有用。
この記事は役に立ちましたか?
関連用語
変更データキャプチャ (CDC) とは - DB の変更をリアルタイム連携するパターン
変更データキャプチャ (CDC、Change Data Capture) はデータベースの INSERT/UPDATE/DELETE を検出して検索エンジンや分析基盤へリアルタイムに伝播させるデータ統合パターン。実装 4 方式と AWS での構成を解説
DynamoDB Streams
DynamoDB テーブルの変更をリアルタイムでキャプチャし、Lambda で処理するイベントソース
CQRS
データの読み取り (Query) と書き込み (Command) を別々のモデルで処理し、それぞれを独立して最適化するアーキテクチャパターン
イベント駆動アーキテクチャ
イベントの発行と購読を中心にシステムを構成し、サービス間の疎結合と非同期処理を実現するアーキテクチャスタイル
イベントストーミングとは - 付箋でドメインを可視化するワークショップ手法
イベントストーミングとは、ドメインエキスパートと開発者が付箋でビジネスプロセスをドメインイベントとして可視化するワークショップ手法。付箋の色の意味、3 つの型の使い分け、境界づけられたコンテキストの発見方法を解説。
Saga コレオグラフィ
各サービスがイベントを発行 / 購読し、中央のオーケストレーターなしで分散トランザクションを実現する方式