ひとことで言うと#
データの現在の状態ではなく、「何が起きたか」というイベントの履歴をすべて保存し、イベントを順に再生することで現在の状態を復元するアーキテクチャパターン。
押さえておきたい用語#
- イベントストア(Event Store)
- イベントを追記専用(Append-Only)で永続化する専用のデータストアのこと。一度記録したイベントは変更・削除しない。EventStoreDBやRDB上に構築可能。
- スナップショット(Snapshot)
- 特定時点の状態を丸ごと保存したキャッシュのこと。イベント数が増えた際のリプレイ性能を最適化するために定期的に作成する。
- プロジェクション(Projection)
- イベントストリームから読み取り用に最適化されたビューを構築する処理のこと。用途別に複数のプロジェクションを作成でき、壊れてもイベントから再構築可能。
- タイムトラベル(Time Travel)
- 過去の任意の時点のイベントまで再生し、その時点の状態を正確に復元する能力のこと。デバッグ・監査・障害分析に強力な機能。
イベントソーシングの全体像#
こんな悩みに効く#
- データの変更履歴を後から追跡できず、問題発生時に原因を特定できない
- 「いつ、誰が、何を変更したか」の監査要件を満たせない
- 過去のある時点の状態を復元したいが、上書き保存しているためできない
基本の使い方#
ドメインで発生するビジネス上の出来事をイベントとして定義する。
- イベントは過去形で命名する(例: OrderPlaced, PaymentReceived, ItemShipped)
- 各イベントに必要な属性を持たせる(発生日時、対象エンティティID、変更内容)
- イベントは不変(immutable)。一度記録したら変更しない
ポイント: イベントは技術的な操作ではなく、ビジネスの出来事として設計する。
イベントを永続化する専用のデータストアを用意する。
- イベントは追記専用(Append-Only)で保存する
- エンティティごとにイベントストリームを管理する
- 各イベントにシーケンス番号を付与し、順序を保証する
ポイント: RDBでもイベントストア専用DB(EventStoreDBなど)でも構築可能。
イベントストリームから現在の状態を再構築するロジックを作る。
- エンティティの初期状態から、イベントを順に適用して現在の状態を得る
- スナップショットを定期的に作成し、復元のパフォーマンスを最適化する
- 特定時点の状態復元(タイムトラベル)も同じ仕組みで実現する
ポイント: イベント数が増えるとリプレイが遅くなるため、スナップショット戦略は必須。
イベントからクエリに最適化されたビュー(プロジェクション)を構築する。
- イベントを購読し、リードモデル(読み取り用DB)を非同期で更新する
- 用途別に複数のプロジェクションを作成できる(一覧表示用、レポート用など)
- プロジェクションが壊れてもイベントから再構築できる
ポイント: CQRSパターンと組み合わせることで、読み書きそれぞれを最適化できる。
具体例#
従来の方法: ordersテーブルにstatusカラムがあり、注文→支払い→発送とUPDATE文で上書き。途中経過が消える。「この注文はいつ支払われたか?」に答えられない。
イベントソーシング適用後:
OrderCreated { orderId: 1001, items: [...], total: 15000 }PaymentReceived { orderId: 1001, method: "credit_card", amount: 15000 }ItemShipped { orderId: 1001, trackingNumber: "JP123456" }
効果: 「支払いはいつ受領されたか」「発送前にキャンセルがあったか」など、すべての経緯が追跡可能に。さらに売上レポート用、在庫管理用、配送追跡用とそれぞれ最適化されたプロジェクションを構築。
要件: 金融庁の監査対応で、「すべての取引の変更履歴を7年間保持し、任意時点の口座残高を復元できること」が必須。
実装:
- すべての取引を
AccountDebited、AccountCredited、TransferInitiated等のイベントとして記録 - 口座残高は「初期残高 + 全イベントの再生」で任意時点の値を正確に復元
- 100イベントごとにスナップショットを自動作成し、復元を高速化
結果: 監査時に「2025年3月15日14:00時点の口座残高」を5秒以内に復元。従来のシステムでは不可能だった時点指定の残高復元を実現し、監査対応コストを年間2,000万円削減。
状況: ECサイトで「在庫があるはずの商品が在庫切れと表示される」バグが発生。在庫テーブルの現在値は正しいように見えるが、過去の経緯が不明。
イベントソーシングによるデバッグ:
- 該当商品のイベントストリームを時系列で表示
- 発見:
InventoryReserved(在庫確保)が2回連続で記録 → 同一注文で在庫が二重確保されていた - 原因: リトライ処理の冪等性が欠如していた
結果: イベントの時系列を辿ることで、30分で根本原因を特定。従来のUPDATE方式では「いつ、何が起きたか」の手がかりがなく、同種のバグの調査に平均4時間かかっていた。
やりがちな失敗パターン#
- すべてのドメインに適用しようとする — イベントソーシングは複雑性が高い。監査・履歴追跡が本当に必要なドメインに絞って適用する
- イベントのスキーマ変更を考慮しない — イベントは不変なので、過去のイベントのスキーマとの互換性が問題になる。イベントのバージョニング戦略を最初から設計する
- 結果整合性を受け入れられない — プロジェクションの更新は非同期。即座に最新状態が必要な画面には向かないことを理解し、UIレベルでの対策(楽観的更新など)を設計する
- スナップショット戦略を後回しにする — イベント数が増えるとリプレイが遅くなる。最初からスナップショットの仕組みを組み込むこと
まとめ#
イベントソーシングは、状態の上書きではなくイベントの蓄積によってシステムの完全な履歴を保持するパターン。監査要件への対応、デバッグのしやすさ、柔軟なデータ活用など多くのメリットがあるが、複雑性も増すため、適用範囲を慎重に選ぶことが成功の鍵となる。