イベント ソーシング パターン

予約

ドメインに、データの現在の状態だけを格納する代わりに、追加専用ストアを使用して、そのデータに対して実行された一連のすべてのアクションを記録します。 ストアは、レコードのシステムとして機能し、ドメイン オブジェクトを具体化するために使用できます。 これにより、データ モデルとビジネス ドメインの同期の必要性を避けることで、パフォーマンス、スケーラビリティ、および応答性を向上させながら、複合ドメインでのタスクを簡略化できます。 さらに、トランザクション データの整合性を提供し、補正アクションを有効にできる完全な監査証跡と履歴を保持することもできます。

コンテキストと問題

ほとんどのアプリケーションはデータを操作します。またアプリケーションの一般的なアプローチは、ユーザーがデータを操作したら、データを更新して、データの最新の状態を維持することです。 たとえば、従来の作成、読み取り、更新、および削除 (CRUD) モデルでの一般的なデータ処理は、ストアからデータを読み取り、何らかの変更を行い、(多くの場合にデータをロックするトランザクションを使用して) データの現在の状態を新しい値で更新することです。

CRUD アプローチにはいくつかの制限があります。

  • CRUD システムは、データ ストアに対して直接更新操作を実行します。 これらの操作は、必要な処理オーバーヘッドのために、パフォーマンスと応答性を低下させ、スケーラビリティを制限する可能性があります。

  • 多くの同時実行ユーザーがいるコラボレーション ドメインでは、更新操作がデータの単一のアイテムに対して行われるため、データ更新の競合が発生する可能性が高まります。

  • 各操作の詳細を個別のログに記録する別の監査メカニズムがない限り、履歴は失われます。

解決策

イベント ソーシング パターンは、一連のイベントによって制御されるデータへの操作の処理方法を定義し、各イベントが追加専用のストアに記録されます。 アプリケーション コードは、データに対して発生した各アクションを強制的に記述する一連のイベントをイベント ストアに送信し、そこでそれらが保持されます。 各イベントは、データに対する一連の変更を表します (AddedItemToOrder など)。

イベントは、データの現在の状態に関するレコードのシステム (権限のあるデータ ソース) として機能するイベント ストアに保持されます。 イベント ストアは、一般にこれらのイベントを公開し、コンシューマーが通知を受け取り、必要に応じてそれらを処理できるようにします。 コンシューマーはたとえば、イベント内の操作を他のシステムに適用するタスクを開始したり、操作を実行するために必要なその他の関連アクションを実行したりすることができます。 イベントを生成するアプリケーション コードは、イベントにサブスクライブするシステムから分離されていることに注意してください。

イベント ストアによって公開されているイベントの一般的な用途は、アプリケーションのアクションによってエンティティが変更されたとき、および外部システムとの統合のために、エンティティの具体化されたビューを保持することです。 たとえば、システムは、UI の一部の入力に使用されるすべての顧客注文の具体化されたビューを保持できます。 アプリケーションは、新しい注文を追加し、注文のアイテムを追加または削除し、配送情報を追加します。 これらの変更を説明するイベントを処理して、具体化されたビューを更新するために使用できます。

任意の時点で、アプリケーションはイベントの履歴を読み取ることができます。 その後、これを使用して、エンティティに関連するすべてのイベントを再生して消費することにより、エンティティの現在の状態を具体化できます。 このプロセスは、要求を処理するときにドメイン オブジェクトを実体化するためにオンデマンドで発生する可能性があります。 または、プレゼンテーション層をサポートするために、エンティティの状態を具体化されたビューとして格納できるように、スケジュールされたタスクを通じてプロセスが発生します。

図は、具体化されたビューの作成、外部アプリケーションおよびシステムとのイベントの統合、特定のエンティティの現在の状態のプロジェクションを作成するためのイベントの再生などのイベント ストリームを使用する場合のいくつかのオプションを含む、パターンの概要を示しています。

イベント ソーシング パターンの概要と例

イベント ソーシング パターンには次の利点があります。

  • イベントは不変であり、追加専用の操作を使用して保存できます。 イベントを開始したユーザー インターフェイス、ワークフロー、またはプロセスは続行でき、イベントを処理するタスクはバック グラウンドで実行できます。 このプロセスは、トランザクションの処理中に競合が発生しないことの組み合わせにより、特にプレゼンテーション レベルやユーザー インターフェイスで、アプリケーションのパフォーマンスとスケーラビリティが大幅に向上する可能性があります。

  • イベントは、イベントによって表されるアクションを記述するために必要な関連データと共に、発生したいくつかのアクションを記述する単純なオブジェクトです。 イベントは直接データ ストアを更新しません。 それらは、適時に処理するために記録されるだけです。 イベントを使用すると、実装と管理を簡素化できます。

  • イベントは一般にドメイン専門家にとって意味がありますが、オブジェクトリレーショナル インピーダンス ミスマッチにより、データベース テーブルが複雑になり、理解しにくくなる可能性があります。 テーブルは発生したイベントを表すのではなく、システムの現在の状態を表す人工的な構造物です。

  • イベント ソーシングは、データ ストア内のオブジェクトを直接更新する必要性を避けるため、同時更新による競合の発生を防ぐのに役立つ可能性があります。 ただし、ドメイン モデルは、不整合な状態になる可能性がある要求から、それ自体を保護するように設計する必要があります。

  • イベントの追加専用ストレージは、データ ストアに対して実行されるアクションを監視するために使用できる監査証跡を提供します。 いつでもイベントを再生することにより、現在の状態を具体化されたビューまたは投影として再生成でき、システムのテストとデバッグを支援できます。 さらに、補正イベントを使用して変更を取り消す必要がある場合は、元に戻された変更の履歴を提供できます。 モデルが現在の状態を保存している場合、この機能は当てはまりません。 イベントのリストを使用して、アプリケーションのパフォーマンスを分析し、ユーザーの行動傾向を検出することもできます。 または、他の有用なビジネス情報を取得するために使用できます。

  • イベント ストアがイベントを発生し、タスクがそれらのイベントに対応して操作を実行します。 このイベントからのタスクの分離により、柔軟性と拡張性を提供します。 タスクは、イベントの種類とイベント データを認識しますが、イベントをトリガーした操作を認識しません。 さらに、複数のタスクで、各イベントを処理することができます。 これにより、イベント ストアによって生成された新しいイベントのみをリッスンする他のサービスやシステムと簡単に統合できます。 ただし、イベント ソーシング イベントは、きわめて低いレベルになる傾向があるため、代わりに特定の統合イベントを生成する必要がある可能性があります。

イベント ソーシングは、イベントに対応して、データ管理タスクを実行することによって、および保存されたイベントからビューを具体化することによって、一般的に CQRS パターンと組み合わされます。

問題と注意事項

このパターンの実装方法を決めるときには、以下の点に注意してください。

システムは、イベントを再生することによって、具体化されたビューを作成するか、データのプロジェクションを生成するときに、最終的にのみ整合されます。 要求の処理の結果としてイベント ストアにイベントを追加するアプリケーション、公開されるイベント、およびそれらを処理するイベントのコンシューマー間にはいくらかの遅延が発生します。 この期間に、エンティティへの追加の変更を記述する新しいイベントがイベント ストアに到着している可能性があります。 システムは、これらシナリオの最終的な整合性を考慮して設計する必要があります。

注意

結果整合性に関する情報については、「Data consistency primer」 (データ整合性入門) をご覧ください。

イベント ストアは情報の永続的なソースであるため、イベント データが更新されてはなりません。 エンティティを更新して、変更を元に戻す唯一の方法は、補正イベントをイベント ストアに追加することです。 おそらく移行時に、永続化されたイベントの形式 (データはなく) を変更する必要がある場合、ストア内の既存のイベントと新しいバージョンを結合することが難しい可能性があります。 変更を行うすべてのイベントを、新しい形式に準拠するように反復処理するか、新しい形式を使用する新しいイベントを追加する必要がある可能性があります。 新旧両方のイベント形式を維持するには、イベント スキーマの各バージョンのバージョン スタンプを使用することを検討してください。

マルチスレッド アプリケーションとアプリケーションの複数インスタンスがイベント ストアにイベントを保存する可能性があります。 イベント ストア内のイベントの整合性は重要であり、特定のエンティティに影響するイベントの順序も重要です (エンティティに対して発生した変更の順序は、その現在の状態に影響します)。 すべてのイベントにタイムスタンプを追加すると、問題の回避に役立ちます。 別の一般的な方法は、要求の結果としての各イベントに、増分識別子で注釈を付けることです。 2 つのアクションが同時に同じエンティティについてのイベントを追加しようとした場合に、イベント ストアは、既存のエンティティ識別子およびイベント識別子に一致するイベントを拒否できます。

イベントを読み取って情報を取得するための標準のアプローチや SQL クエリのような既存のメカニズムはありません。 抽出できる唯一のデータは、条件としてイベント識別子を使用するイベントのストリームです。 イベント ID は一般に個別のエンティティにマップします。 エンティティの現在の状態は、そのエンティティの元の状態に照らして、それに関連するすべてのイベントを再生することによってのみ判断できます。

各イベント ストリームの長さは、システムの管理と更新に影響します。 ストリームが大きい場合、指定した数のイベントなどの特定の間隔でスナップショットを作成することを検討してください。 エンティティの現在の状態は、スナップショットから、および特定の時点以降に発生したイベントを再生することにより、取得できます。 データのスナップショットの作成方法の詳細については、プライマリ/下位スナップショット レプリケーションに関する記事をご覧ください。

イベント ソーシングは、データへの更新の競合の可能性を最小にしますが、それでもなおアプリケーションでは結果整合性とトランザクションの欠如の結果としての不整合を処理できる必要があります。 たとえば、在庫の減少を示すイベントは、その品目の注文中にデータ ストアに到着する場合があります。 この状況では、顧客にアドバイスするか、バック オーダーを作成することによって、2 つの操作を調整する必要があります。

イベントの公開は「少なくとも 1 回」になる可能性があるため、イベントのコンシューマーはべき等である必要があります。 それらは、イベントが複数回処理される場合に、イベントに記述されている更新を再適用すべきではありません。 コンシューマーの複数のインスタンスが、発注の総数などのエンティティのプロパティを維持および集計できます。 発注イベントが発生したときに、集計のインクリメントに成功する必要があるのは 1 つだけです。 この結果はイベント ソーシングの重要な特徴ではありませんが、通常の実装上の決定です。

選んだイベント ストレージは、アプリケーションによって生成されるイベントの負荷をサポートする必要があります。
1 つのイベント処理に 1 つ以上の新しいイベント作成を伴うシナリオに注意してください。無限ループを引き起こす可能性があります。

このパターンを使用する状況

このパターンは、次のシナリオで使用します。

  • データ内のインテント、目的、または理由をキャプチャする場合。 たとえば、顧客エンティティの変更を、転居アカウントの削除、または死亡などの一連の特定のイベントの種類としてキャプチャできます。

  • データへの更新の競合の発生を最小限に抑えるか、完全に避けることが不可欠な場合。

  • 発生したイベントを記録したり、それらを再生してシステムの状態を復元したり、変更をロールバックしたり、履歴と監査ログを保持したりする場合。 たとえばタスクに、更新を元に戻すアクションを実行するために必要な複数のステップが含まれ、さらに、データを整合性のある状態に戻すいくつかのステップを再生する場合などです。

  • イベントを使用する場合。 これは、アプリケーションの操作の自然な機能であり、追加の開発や実装の作業はほとんど必要ありません。

  • これらのアクションを適用するために必要なタスクから、データの入力や更新のプロセスを分離する必要がある場合。 この変更は、UI パフォーマンスが向上したり、イベントが発生したときにアクションを実行する他のリスナーにイベントを配布したりすることができます。 たとえば、給与システムと経費申請 Web サイトを統合できます。 Web サイトで行われたデータ更新に応答してイベント ストアによって発生したイベントは、Web サイトと給与システムの両方で使用されます。

  • 具体化されたモデルとエンティティ データの形式を変更できるようにする柔軟性が必要な場合、または CQRS と共に使用する場合は、データを公開する読み取りモデルまたはビューを適合させる必要があります。

  • CQRS と共に使用し、読み取りモデルの更新中に、結果整合性が許容できる場合、またはイベント ストリームからのエンティティおよびデータのリハイドレート中のパフォーマンスへの影響が許容できる場合。

このパターンは、次の状況では有効でない場合があります。

  • 小規模または単純なドメイン、ビジネス ロジックがほとんどまたはまったく含まれていないシステム、または従来の CRUD データ管理メカニズムと自然にうまく連携する非ドメイン システム。

  • データのビューの整合性とリアルタイムの更新が必要なシステム。

  • 監査証跡、履歴、およびアクションをロールバックし、再生する機能が必要でないシステム。

  • 基になるデータへの更新の競合の発生が少ないシステム。 たとえば、データを更新せずに、ほとんどデータを追加するシステムなどです。

ワークロード設計

設計者は、Azure Well-Architected Framework の柱で説明されている目標と原則に対処するために、イベントソーシングパターンをワークロードの設計でどのように使用できるかを評価する必要があります。 次に例を示します。

重要な要素 このパターンが柱の目標をサポートする方法
信頼性設計の決定により、ワークロードが誤動作に対して復元力を持ち、障害発生後も完全に機能する状態に回復することができます。 複雑なビジネスプロセスの変更履歴をキャプチャするため、ステートストアを回復する必要がある場合は、ステート再構築が容易になります。

- RE:06 データパーティショニング
- RE:09 災害復旧
パフォーマンスの効率化は、スケーリング、データ、コードを最適化することによって、ワークロードが効率的にニーズを満たすのに役立ちます。 通常、このパターンは CQRS、適切なドメイン設計、および戦略的スナップショットと組み合わせることにより、アトミック 付録のみの操作や書き込みと読み取りのためのデータベースロックを回避することによってワークロードパフォーマンスを向上させることができます。

- PE:08 データパフォーマンス

設計決定と同様に、このパターンで導入される可能性のある他の柱の目標とのトレードオフを考慮してください。

会議管理システムは、会議の完了した予約の数を追跡する必要があります。 このようにして、潜在的な参加者が予約しようとしたときに、まだ空席があるかどうかを確認できます。 システムでは、少なくとも 2 つの方法で、会議の予約の合計数を格納できます。

  • システムは、予約情報を保持するデータベース内の個別のエンティティとして、予約の合計数に関する情報を格納できます。 予約が行われるか、取り消されると、システムは必要に応じてこの数を増分または減分できます。 このアプローチは理論上は単純ですが、多数の出席者が短時間に席を予約しようとした場合に、スケーラビリティの問題が発生する可能性があります。 たとえば、予約期間が終わる前の最終日などです。

  • システムは、イベント ストアに保持されるイベントとして、予約と取り消しに関する情報を格納できます。 さらに、これらのイベントを再生することにより、空席数を計算できます。 このアプローチは、イベントの不変性のため、スケーラビリティが向上する可能性があります。 システムは、イベント ストアからデータを読み取ったり、イベント ストアにデータを追加したりできる必要があるだけです。 予約と取り消しに関するイベント情報が変更されることはありません。

次の図は、イベント ソーシングを使用して、会議管理システムの座席予約サブシステムを実装する方法を示しています。

会議管理システムで、座席予約に関する情報をキャプチャするためのイベント ソーシングの使用

2 つの座席を予約するためのアクションのシーケンスは次のようになります。

  1. ユーザー インターフェイスは、2 名の出席者の座席を予約するコマンドを発行します。 コマンドは、個別のコマンド ハンドラーによって処理されます。 ユーザー インターフェイスから分離され、コマンドとして投稿された要求の処理を担当するロジックの一部。

  2. 予約と取り消しを示すイベントをクエリすることによって、会議のすべての予約に関する情報を含む集計が構築されます。 この集計は、SeatAvailability と呼ばれ、集計内のデータのクエリと変更のメソッドを公開するドメイン モデル内に含まれます。

    考慮すべきいくつかの最適化は、スナップショットの使用 (集計の現在の状態を取得するために、すべてのイベントをクエリし、再生する必要がないように) とメモリ内に集計のキャッシュされたコピーを保持することです。

  3. コマンド ハンドラーは、ドメイン モデルによって公開されたメソッドを呼び出し、予約を行います。

  4. SeatAvailability 集計は予約された座席数を格納するイベントを記録します。 次回にイベントに集計が適用されたときに、すべての予約を使用して、残席数が計算されます。

  5. システムは、イベント ストア内のイベントの一覧に新しいイベントを追加します。

ユーザーが座席をキャンセルした場合、コマンド ハンドラーが座席取り消しイベントを生成し、それをイベント ストアに追加することを除いて、システムは同様のプロセスに従います。

スケーラビリティのためのスコープの拡大に加えて、イベント ストアを使用することで、会議の予約と取り消しの完全な履歴または監査証跡も提供します。 イベント ストア内のイベントは、正確なレコードです。 システムはイベントを簡単に再生し、任意の時点に状態を復元できるため、他の方法で集計を保持する必要はありません。

この例の詳細については、「Introducing Event Sourcing」 (イベント ソーシングの導入) をご覧ください。

次のステップ

このパターンを実装する場合は、次のパターンとガイダンスも関連している可能性があります。

  • コマンド クエリ責務分離 (CQRS) パターン。 CQRS 実装についての情報の永続的なソースを提供する書き込みストアは、イベント ソーシング パターンの実装に基づくことがあります。 個別のインターフェイスを使用して、データを更新する操作から、アプリケーションでデータを読み取る操作を分離する方法について説明します。

  • Materialized View Pattern (具体化されたビュー パターン) イベント ソーシングに基づいて、システムで使用されるデータ ストアは、一般に効率的なクエリに適していません。 代わりに、一般的なアプローチは、一定の間隔で、またはデータが変更されたときに、データの事前設定済みのビューを生成することです。

  • Compensating Transaction パターン。 イベント ソーシング ストアの既存のデータは更新されません。 代わりに、エンティティの状態を新しい値に遷移させる新しいエントリが追加されます。 変更を元に戻すには、以前の変更を元に戻すことができないため、補正エントリを使用します。 以前の操作によって実行された作業を元に戻す方法について説明します。