イベントソーシングがあなたのアプリケーションの状態を追跡するのにどのように役立つか
イベントソーシングは、アプリケーションの状態の変更が永続的に保存される「イベント」の系列としてキャプチャされるソフトウェアアーキテクチャです。ほとんどのシステムが現在の状態のみを公開するのに対し、イベントソーシングはすべての以前の状態の完全な記録を作成します。
基本原則
イベントソーシングは、最も一般的には時間や線形プロセスベースのシステムで使用されます。ショップの注文アプリでは、取引が「保留」、「承認」、「出荷」の各状態に遷移することがあります。各状態間の遷移は、その注文のライフサイクルにおける独自の「イベント」です。
このシステムは従来のリレーショナルデータベースを使用してモデル化できます:
order_id | order_status
---------|-------------
1000 | APPROVED
ほとんどのシステムでは、現在の状態を知るだけでは不十分です。注文が承認された日時も知りたいです。これを追加のフィールドを加えることで実現できます:
order_id | order_created_time | order_approved_time | order_shipping_time
---------|--------------------|---------------------|---------------------
1000 | 2021-04-30 | 2021-05-01 | NULL
今、注文が4月30日に作成され、5月1日に承認されたことは明らかであり、配送はまだ保留中です。多くのアプリケーションにとって、この構造はうまく機能します。ただし、州が追加されるにつれて制約が生じる可能性があります。
では、イベントソーシングを使用するように構造を変更したこの例を見てみましょう。
event_id | event_order_id | event_type | event_timestamp
---------|---------------------|----------------|----------------
2 | 1000 | order.approved | 2021-05-01
1 | 1000 | order.created | 2021-04-30
命令の状態遷移は、専用のイベントログに分離されています。このモデルにより、将来的に新しいイベントタイプを追加することが簡単になります。顧客が注文をキャンセルする必要がある場合、order.cancelledイベントをログに書き込むことができます。
イベントソーシングは、注文自体に関するデータを追跡するためにも使用できます。order.apply_discountやorder.process_refundのようなイベントタイプを追加することができます。いつでもイベントを記録でき、注文の新しい状態を作成しながら、以前の状態にアクセスできるようにしておくことができます。
オブジェクトの状態を再構築する
オブジェクトの現在の状態は、それに関連するすべてのイベントを取得することで判断できます。注文が承認されたかどうかを確認したい場合は、そのイベントコレクションに order.approved イベントが含まれているかどうかをチェックしてください。
歴史的な状態をまったく同じ方法で再構築できます。特定の日付における秩序の状態を知りたい場合、その日またはそれ以前に記録されたすべてのイベントを取得してください。
イベントソーシングは、オブジェクトの時系列を自動的に追跡する増分モデルです。オブジェクトの状態に対するすべての変更は、タイムスタンプ付きのイベントとして記録されるべきです。イベントソーシングを使用すると、自動的なバージョン管理と状態遷移の完全な監査ログが得られます。
オブジェクトの履歴を再構築する必要がある場合、特定の日付以降に作成されたイベントを破棄することができます。悪意のある行為者によってユーザーのアカウントが侵害され、その者の名のもとにさまざまな行動が取られたとしましょう。完全にイベントソース化されたシステムでは、そのユーザーに関連する疑わしい時間枠内のイベントを削除することができます。これにより、彼らのアカウントを良好な状態に回復させることができます。
この能力を維持するためには、イベントが不変であることを確認する必要があります。イベントが記録されたら、そのプロパティを決して変更してはいけません。もしイベントに調整が必要な場合は、その変更を反映させるために別のイベントを記録します。
システムの不変性は、時間を遡って歴史的な状態を再構築するために安全に使用できることを意味します。もし2年前のようにデータベースを再現したい場合は、その間に記録されたすべてのイベントを破棄することができます。
イベントソーシングのもう一つの利点は、システムの状態への可視性が向上することです。ユーザーがバグを報告した場合、データベースをクローンし、新しいイベントを破棄し、既知の良好なポイントからの手順を繰り返すことができます。ログされたイベントを分析することで、コードベースのバグを特定するのに役立ちます。
実世界におけるイベントソーシング
イベントソーシングは、複雑で扱いづらく、過剰に複雑であるという評判があります。歴史的に見ると、イベントソーシングは厳格な監査要件を持ち、イベントを再生する必要が証明されたアプリケーションに関連付けられてきました。
ただし、必ずしもそうである必要はありません。経験豊富な開発者であれば、過去にイベントソーシングに近いものを実装したことがあるでしょう。「イベント」の記録を保持するシステム——ユーザーログイン試行、ウェブサイトページヒット、または注文処理の段階など——は、たとえ意図的に実装していなくても、自然にイベントソーシングアプローチに向かうものです。
イベントソーシングをコードで意図的に実装することは面倒な場合があります。開発者は通常、データベースから取得したデータがオブジェクトの現在の状態を正確に表していると仮定します。しかし、イベントソーシングでは、取得したデータ自体の価値はほとんどありません。状態の表現を作成するために、コードベースでイベントを「再生」する必要があります。
イベントソーシングはパフォーマンスオーバーヘッドを引き起こす可能性があります。上記の例では、オーダーオブジェクトの完全な表現を作成するためには、はるかに多くのデータを取得する必要があります。従来のモデルでは、単一の選択でオーダーに関連するすべてのデータが提供されます。
他にもいくつかの欠点があります。何か問題が起こった場合、イベントソーシングの修正は難しくなることがあります。迅速なパッチを作成したり、手動でデータベースをホットフィックスしたりすることはできません。イベントスキーマを適切に移行し、歴史的な年代が保持されるようにする必要があります。
イベントソーシングとCQRSの組み合わせ
イベントソーシングは、一般的にCQRS(コマンドクエリ責任分離)と組み合わせて使用されます。このパターンは、データを書き込むコマンドとデータを読み取るクエリを分離することを提唱しています。
イベントソーシングの使用は、CQRSのある程度を必要とします。データはイベントを介してデータベースに書き込まれます。したがって、書き込みモデルは非常にシンプルです。それは発生したイベントの一方向の追加のみのログです。イベントはCQRSで説明される「コマンド」の具現化です。
データ取得モデルは完全に独立しています。最も適切なクエリシステムを使用してイベントを取得し、それをアプリケーションの現在の状態に重ね合わせることができます。コマンド(イベント)はシステムを新しい状態に遷移させ、クエリはアプリケーションが要求する状態の側面を明らかにします。
言い換えれば、読み取りモデルと書き込みモデルは互いにどのように機能しているかを認識していません。あなたのコードベースのエンティティ(たとえば、注文)は、時系列のイベントのシーケンスとして保存されます。保存されたイベントは、そのエンティティの状態がどのように導出されるかの知識がないと、関連するエンティティに再構築できません。その知識は、データをアプリケーションに戻す読み取り(クエリ)モデル内で実装されています。
この特徴により、永続層は各操作に対して単一のレコード挿入に簡素化されます。格納されたデータは、クエリモデルが後でそれを操作するため、特定のエンティティを正確に表す必要はありません。これは、テーブルフィールドがコードベース内のオブジェクトのプロパティとしばしば密接に一致する「従来の」リレーショナルデータベースとは対照的です。
クエリモデルの出力は「プロジェクション」として知られています。プロジェクションは、データをそのストレージシステムとは異なる視点で表現するものです。イベントソーシングを使用する際、データはイベントのストリームとして保存されますが、アプリケーションの現在の状態の表現に投影されます。その表現はステートレスで、不変で、冪等性があります - 表現を作成することによって、基盤となるイベントデータがいかなる方法でも変更されることはありません。
単一のプロジェクションは、いくつかの異なるイベントタイプと相互作用する必要があるかもしれません。プロジェクションは、アプリケーションの機能にとって意味のある方法で、データを集約的に見ることができます。前の例では、オーダープロジェクションは、対象となる注文に関連するイベントを調べることにより、CreatedDate、ApprovedDate、およびShippedDataプロパティを含むオブジェクトを出力することができます。
結論
イベントソーシングは、アカウンタビリティ、年表、状態再現のために内在的なサポートを持つ特化したソフトウェアアーキテクチャです。このパターンは、これらの特性が望ましいアプリケーションの実装を大幅に簡素化することができます。
イベントソーシングは他のシナリオでも有用ですが、効果の減少が見られることがあります。イベントソーシングを採用するには、開発者が現在の状態を判断するためのコードを実装する必要があり、これによりデータベースに現在の状態だけを保存するシステムには発生しない追加のオーバーヘッドが生じます。
イベントソーシングは、CQRS、オブザーバーパターン、最終的整合性などの他のソフトウェアアーキテクチャ手法と組み合わせるのが最適です。アプリ全体にわたってイベントソーシングを使用する必要はありません。多くの場合、個々のサブモジュールはイベントソーシングの恩恵を受ける一方で、プロジェクトの大部分は伝統的なリレーショナルデータベースを使用します。
