イベントソーシング:基本
イベントソーシングとは
イベントソーシング(ES)は、ドメインオブジェクトの状態を「現在の行」としてではなく、起こったすべてを説明する変更できないイベントログとして保存する方法です。集計の現在の状態は、イベントのロールアップ(リプレイ)によって取得され、読み取りビューはこのログの上に投影として構築されます。
重要な意味:- 歴史は「真理の主要な源」であり、状態は歴史の投影である。
- 任意の状態を複製、チェック、説明(監査)することができます。
- 新しいビューやアナリティクスを追加するには、古い「スナップショット」の移行は必要ありません。
基本用語
集計-明確な不変量(Order、 Payment、 UserBalance)との整合性のドメインユニット。
イベント-過去に発生した不変の事実('payment。認可された'、'順序。')を出荷しました。
Event Storeは、ユニット内のイベントの順序を提供する付録専用のログです。
集計バージョンは、最後に適用されたイベントの数です(楽観的な並列処理の場合)。
スナップショット-畳み込みを高速化する状態の定期的な印象。
投影(read-model)-読み取り/検索/レポート用の実体化ビュー(しばしば非同期)。
その仕組み(スレッド→イベント→投影)
1.クライアントはコマンド('CapturePayment'、 'PlaceOrder')を送信します。
2.集計は不変量を検証し、すべてがOKならイベントを生成します。
3.Event Storeには、バージョン検証(楽観的な並列処理)でイベントがアトミックに追加されます。
4.プロジェクションプロセッサはイベントフローを購読し、読み取りモデルを更新します。
5.次のコマンドで集計がロードされると、ステータスはスナップショット(もしあれば)→スナップショット後のイベントに復元されます。
イベントデザイン
必須属性(コア)
json
{
"event_id": "uuid",
"event_type": "payment. authorized. v1",
"aggregate_type": "Payment",
"aggregate_id": "pay_123",
"aggregate_version": 5,
"occurred_at": "2025-10-31T10:42:03Z",
"payload": { "amount": 1000, "currency": "EUR", "method": "card" },
"meta": { "trace_id": "t-abc", "actor": "user_42" }
}
推奨事項:
- 名前:'ドメイン。アクション。v {major}'。
- Additivity:新しいフィールドは、古いフィールドの意味を変更することなく、オプションです。
- ミニマリズム:簡単に回復可能なデータを複製することなく、事実のみ。
契約とスキーム
スキーマ(Avro/JSON Schema/Protobuf)を修正し、CIの互換性を確認します。
「breaking」の変更-イベントの新しいメジャーバージョンと移行期間の並行出版物'v1'/'v2'。
競争力のあるアクセス: 楽観的な並行性
ルール:新しいイベントは'expected_version==current_version'の場合にのみ記述できます。
擬似コード:pseudo load: snapshot(state, version), then apply events > version new_events = aggregate. handle(command)
append_to_store(aggregate_id, expected_version=current_version, events=new_events)
//if someone has already written an event between load and append, the operation is rejected -> retray with reload
したがって、分散トランザクションなしで不変量の完全性を保証します。
スナップショット(畳み込み加速)
Nイベントまたはタイマーごとにスナップショットを撮ります。
'snapshot_state'、 'aggregate_id'、 'version'、 'created_at'をХранитеします。
スナップショットの後にイベントを常にチェックして追いつく(キャストを信頼するだけではありません)。
ログから再作成できるようにスナップショットを削除します(「magic」フィールドは保存しないでください)。
投影とCQRS
ESは自然にCQRSと組み合わされます:- Write-model=aggregates+Event Store。
- Read models=projections updated by events (Redis cards、 OpenSearch for search、 ClickHouse/OLAP for reporting)。
- 投影はidempotentです:同じ'event_id'を再処理すると、結果は変更されません。
回路の進化と互換性
Additive-first:フィールドを追加します。型/セマンティクスを変更しないでください。
複雑な変更の場合は、新しいイベントタイプをリリースし、プロジェクションマイグレータを書き込みます。
遷移期間にダブルエントリ('v1'+'v2')を維持し、すべての投影が準備できたら'v1'を撮影します。
安全性、PIIと「忘れられる権利」
履歴には機密データが含まれていることが多い。アプローチ:- イベントのPIIを最小化します(データの代わりに識別子、保護された側面の詳細)。
- Crypto-erase:フィールドを暗号化し、削除を促すと、キーを破棄します(イベントは残りますが、データは利用できません)。
- リビジョンイベント:'user。ピリッとした。v1'と投影中の機密フィールドの置換(履歴は編集の事実を保持します)。
- 保存ポリシー:一部のドメインでは、一部のイベントをWORMストレージにアーカイブできます。
パフォーマンスとスケール
パーティショニング:順序は'agregate_id'による集計パーティション内で重要です。
コールドスタート:スナップショット+定期的な「圧縮」畳み込み。
バッチ付録-1つのトランザクションでイベントをグループ化します。
投影プロセッサ用のバックプレッシャーおよびDLQ;遅延(メッセージの時間と数)を測定します。
イベントストアインデックス:'(aggregate_type、 aggregate_id)'と時間によるクイックアクセス。
テスト
集計の仕様テスト-「commands→expected events」シナリオ。
投影テスト:イベントフローをフィードし、実体化された状態/インデックスを確認します。
再現性テスト:スタンド上の投影をゼロから再構築する-結果が一致することを確認します。
カオス/レイテンシー:遅延とテイクを注入し、idempotenceをチェックします。
ドメインの例
1)支払い
イベント:'支払い。'、'支払いを開始しました。承認された'、'支払。キャプチャされた'、'支払い。返金されました。
不変量:'authorized'なしで'キャプチャ'することはできません。量は負ではありません。通貨は変わらない。
投影:「支払いカード」(KV)、トランザクション検索(OpenSearch)、レポート(OLAP)。
2)注文(電子商取引)
イベント: 'order。'、'を配置しました。有料、注文。詰められた'、'順序。出荷された'、'順序。納品されました'.
不変量:状態チャートの状態遷移;キャンセルは'出荷'の前に可能です。
投影:ユーザー注文のリスト、ステータス別のSLAボード。
3)バランスシート(ファイナンス/iGaming)
イベント:'バランス。「預金」「残高」引き落とし'、'バランス。クレジットされました。調整されました。
ハードインバリアント:バランスがなくならない<0;コマンドは'operation_id'です。
重要な操作は、集計(厳密な一貫性)、UI-投影(最終的な)から直接読み込まれます。
典型的なイベントストア構造(DBバリアント)
イベント
'event_id (PK)'、 'aggregate_type'、 'aggregate_id'、 'version'、 'event_at'、 'event_type'、 'payload'、 'meta'
インデックス:'(aggregate_type、 aggregate_id、バージョン)'。
スナップショット
'aggregate_type'、 'aggregate_id'、 'version'、 'state'、 'created_at'
インデックス:'(aggregate_type、 aggregate_id)'。
consumers_offsets
'consumer_id'、 'event_id'/'position'、 'updated_at'(投影および小売用)。
よくあるご質問(FAQ)
どこでもESを使用することは必須ですか?
いいえ、そうではありません。ESは、監査、複雑な不変量、再現性、およびデータのさまざまな表現が重要である場合に便利です。単純なCRUDの場合、これは冗長です。
「現在の状態」リクエストはどうですか?
投影から読み取るか(素早く、最終的に)、ユニットから読み取るか(より高価ですが、厳密には)。通常、重要な操作は2つ目のパスを使用します。
Kafka/Streamブローカーは必要ですか?
イベントストア-真実の源;ブローカーは、プロジェクターや外部システムにイベントを配布するのに便利です。
「忘れられる権利」をどうすればよいでしょうか"?
PIIを最小限に抑え、機密フィールドを暗号化し、投影に暗号消去/redactionを適用します。
古いデータを移行するにはどうすればよいですか?
retrospectiveイベント生成のスクリプト(「re-highstory」)を書くか「、state-as-is」から始めて、新しい変更のためにのみイベントを公開します。
アンチパターン
イベントソーシング「out of habit」:ドメインの利益なしでシステムを複雑化させます。
脂肪のイベント:PIIとダブルスで膨らんだペイロード-ブレーキとコンプライアンスの問題。
楽観的な並行性の欠如:レース時の不変量の損失。
再現性のない投影:再生/スナップショットなし→手動修正。
ドメインイベントとしてのRaw CDC:リークされたDBスキーマとハード接続。
内部イベントと統合イベントのミキシング:安定した「ショーケース」を外部に公開します。
生産チェックリスト
- 集計、不変量、イベント(タイトル、バージョン、スキーマ)が定義されます。
- Event Storeは、集計と楽観的な並行性の中で注文を提供します。
- スナップショットとその再構築プランが含まれています。
- 投影はidempotentであり、DLQとラグメトリックがあります。
- スキームはCIで検証され、バージョンポリシーは文書化されています。
- PIIは最小化され、フィールドは暗号化され、「忘却」戦略があります。
- 投影リプレイはベンチでチェック;災害復旧計画を持っています。
- ダッシュボード:アプリの速度、投影遅延、アプリケーションエラー、レトレイの割合。
合計
イベントソーシングは、システムの歴史を一流のアーティファクトにします。事実を捉え、それらから状態を再現し、自由に表現を構築します。これにより、監査、変更への抵抗、分析の柔軟性が得られます。これは、スキームの規律、競争力のある管理、機密データの有能な作業の対象となります。