年 9 月 2015

ボリューム 30 番号 9

Cutting Edge - 一般的なアプリケーション向けのイベント ソーシング

Dino Esposito | 年 9 月 2015

Dino Espositoそれほど深く考えるまでもなく、データ ストレージとは「単にデータの現在状態を保持する形式」と見るのが自然です。保険や銀行業界では、ソフトウェアの動作を細かく追跡、記録する大規模システムも見られますが、ほとんどのアプリケーションや Web サイトではデータの現在状態を保持すれば十分です。

現在状態を保持するには、システム状態のスナップショットを取り、永続化します。このデータは通常、リレーショナル データベースに格納します。新しいトランザクションを実行したり、過去のトランザクションの結果を取得する場合は、これで十分です。過去 10 年、現在状態の保持だけでは不十分なシナリオは、あまりありませんでした。

ところが最近、ビジネスの様相が急速に変化し、ビジネスやドメインのイベントを追跡することはむしろ当たり前になってきました。イベント ソーシング (ES) という 1 つのパターンは、ストレージ アーキテクチャと、格納されているデータを取得、挿入する方法に影響を与えます。ES は、永続化したドメインにおいてビジネス関連のイベントを監査および記録するだけでなく、抽象化のレベルを下げてデータを保存し、アドホックなツールとパターンを使用して、複数のデータ プロジェクションを作成します。

ES は、ビジネス機能をログに記録して監査する優れた方法に見えますが、実際は、当初からリレーショナル モデルとの関係が深い、ストレージ モデルの新しい理論です。さらに、最新のソフトウェアに対して NoSQL ストレージよりも大きな影響を与えます。ES は、今日存在するリレーショナル製品や NoSQL 製品の機能を置き換えるものではありません。ES は、リレーショナル データベースと NoSQL データ ストアの上位に実装するものです。ES は、アプリケーション ストレージへの見方を変え、データのトークンとしてステートフルな値ではなくイベントを使用します。

イベント ソーシングの役割

現在状態の保持だけでは不十分なシナリオに対応するには、更新履歴の追跡を実装するのが基本的かつ一般的です。ここにシンプルな書店アプリケーションがあるとします。各書籍に description プロパティを用意し、ユーザーが編集できるようにします。ここで、ユーザーが新しい description を入力したときに、古い description を追跡するかどうか考えます。

この場合さまざまな要件が考えられますが、今回は更新の追跡は欠かせないことにします。これを実装するには、どうすればよいでしょう。1 つの選択肢は、書籍の現在状態を格納し、更新の詳細については別のテーブルにログ記録する方法です。この場合、更新のたびに 1 つのレコードを作成することになります。更新レコードには、変更対象の各列の古い値と新しい値など、更新の差分を含めます。

もう 1 つ選択肢があります。同じ書籍の複数のレコードを、特定の ID でマークを付けて Books テーブルに含める方法です。各レコードは、順番に並べられたタイムスタンプ付きの状態で表現します (図 1 参照)。

エンティティの履歴を保持する複数のレコード
図 1 エンティティの履歴を保持する複数のレコード

このシナリオでは、レコードの現在状態を読み取るアドホックな API が必要になります。ID によってレコードを選択するようなリポジトリ内のクエリでは不十分です。タイムスタンプが最新のもの、または更新後の連続番号が最も大きいものを選択する必要があります。また、特定のデータ エンティティに関連するすべてのイベントの集合体がストリームを形成します。このイベント ストリームが ES での一般的な考え方です。

ES は、ビジネスで一連のイベントを追跡しなければならないときに役立ちます。ES はログ記録や監査などに関する横断的問題のように見えるかもしれませんが、そうではありません。例外をプロファイリングまたは追跡するためにイベントをログ記録するのではなく、ビジネス イベントを追跡するだけです。横断的な問題ではなく、主にストレージに適用されるアーキテクチャ上の決定です。

イベント ソーシングの定義

簡単に言うと、主要データ ソースとしてイベントを使用するのが ES です。ES は必ずしもすべてのアプリケーションに適切なわけではないため、ここ数十年間、開発者が取り上げることはありませんでした。ES が今でも役に立たないように思えるとしたら、それはおそらく、ES をまだ必要としていないのだと思います。

ここでは、ES の必要性を次のようにまとめます。ES とは、ドメインの専門家が、ソフトウェアによって生成されるイベントのシーケンスを追跡する場合に有効な方法です。それ以外の場合でも、ワークフローを表現し、ビジネス ロジックの各部を連結するのにイベントが有効ですが、この場合は、イベントがドメインの主役ではなく、永続化されない場合もあります。これが、現在主流のシナリオです。

それでは、アプリケーションの主要データ ソースがイベントの場合を考えてみます。ES は、永続化とクエリというストレージの 2 つの側面に影響します。永続化を特徴付けているのは、挿入、更新、および削除という 3 つの主要操作です。ES のシナリオでも、挿入は、エンティティの現在状態を永続化する従来のシステムとほぼ同じです。システムが要求を受け取り、新しいイベントをストアに書き込みます。イベントには、(GUID などの) 一意識別子、型名、または (イベントの種類、タイムスタンプ、および関連情報を識別する) コードが含まれます。

更新とは、同じデータ エンティティのコンテナー内で別の挿入を行うことです。新しいエントリは単にそのデータを反映します。この場合のデータとは、具体的には「変更したプロパティ」、「新しい値」、および「(ビジネス ドメインに関連する場合は) プロパティの変更理由とその方法」です。更新を実行すると、データ ストアが拡張されます (図 2 参照)。

ID #1 のエンティティへの更新を示す新しいレコード
図 2 ID #1 のエンティティへの更新を示す新しいレコード

削除操作は更新と同じ方法で機能しますが、保持する情報が異なるため、削除操作であることが明確になります。

このような更新方法では、クエリに関する問題がいくつか生じます。特定レコードの存在の有無、またはその現在状態について知るにはどうすればよいでしょう。これには、クエリを実行するアドホックな層が必要です。考え方としては、一致する ID を持つすべてのレコードを選択後、イベントの順序に従ってデータ セットを分析します。

たとえば、Created イベントのコンテンツに基づいて新しいデータ エンティティを作成します。次に、すべての連続手順を再生して、ストリームの最後に残ったものを返します。この手法を、イベントの再生といいます。ただし、状態を再構築するためにイベントを単純に再生すると、パフォーマンスの問題が生じます。

銀行口座について考えてみます。銀行口座を開設してから数年が経った顧客には、開設以降、数百もの操作とイベントが蓄積されていきます。現在残高を取得するためには、何百件もの操作を再生して、口座の現在状態を再構築する必要があります。これは、必ずしも実用的ではありません。

このシナリオの回避策は複数ありますが、スナップショットを作成する回避策が最も重要です。スナップショットとは、ある時点のエンティティの既知の状態を保存するレコードです。この方法だと、スナップショット以前の日付のイベントを再生する必要がありません。

ES は、特定のリレーショナル データベースや NoSQL データ ストアなど、テクノロジや製品に縛られませんが、「イベント ストア」という特別なソフトウェア コンポーネントが必要です。イベント ストアとは、実質的にはイベント ログです。任意のデータ ストア API の上位で独自のコードを使用して作成することを可能にします。

イベント ストアには、主な特性が 2 つあります。まず、イベント ストアは追加のみのデータ ストアで、更新をサポートしません。特定の種類の削除については、オプションでサポートする場合があります。次に、イベント ストアは、特定のキーに関連付けられているイベントのストリームを返すことができなければなりません。このコードの層は自作しても、一般に利用できるツールやフレームワークを使用してもかまいません。

イベント ストアの実装方法

イベント ストアの実装には、使えるものは何を使ってもかまいませんが、永続化エンジンには、リレーショナル データベースや NoSQL データ ストア風のものを使用する場合がほとんどです。リレーショナル データベースを使用する場合は、イベントごとに 1 行生成するテーブルを、エンティティの種類につき 1 つ保持します。

イベントには通常、複数のレイアウトがあります。たとえば、各イベントは保存するプロパティ数がそれぞれ異なるので、すべての行に共通するスキーマを特定するのは困難です。すべての列の集合体に起因する共通のスキーマが存在し得て、適切に実行されるならば、イベント ストアを簡単に実装することができます。

SQL Server 2014 の列ストア インデックス機能を使用して、横の行ではなく、縦の列にデータを格納するようテーブルを構成することもできます。また、どのバージョンの SQL Server にも使えるのが、JSON オブジェクトにイベント プロパティを正規化し、それを 1 つの列に文字列として格納する方法です。

NoSQL の専門用語では、「ドキュメント」とは、可変数のプロパティを持つオブジェクトのことです。NoSQL 製品によっては、ドキュメントの格納に特化するものもあります。開発者の観点からは、これほど簡単なものはありません。クラスを作成して、値を挿入し、そのまま格納するだけです。クラスの型は、複数のイベントを結び付ける重要な情報です。NoSQL を使用する場合は、1 つのイベント オブジェクトを保存するだけです。

進行中のプロジェクト

ES は、比較的新しいアーキテクチャ手法です。同時に、イベントベースのデータ ストア上でコードを記述するための標準ツールは、今でも進化を続けています。自身で ES ソリューションを準備することももちろん可能ですが、アドホックなツールを使用して、より構造化された方法でイベントを保存することもできます。

イベントを認識するデータ ストアを使用する主なメリットは、イベントの読み取りと追加のみの実行がデータベースなどのツールによって保証されると同時に、イベント ソーシング手法のビジネス整合性も保証されることです。イベントの格納に特化して設計されたフレームワークの 1 つが、NEventStore プロジェクトです (neventstore.org、英語)。このフレームワークは、イベントの読み取りと書き込みを再度実行することを可能にし、永続化に依存しないで動作します。イベントを保存する方法を次に示します。

var store = Wireup.Init()
  .UsingSqlPersistence("connection")
  .InitializeStorageEngine()
  .UsingJsonSerialization()
  .Build();
var stream = store.CreateStream(aggregateId);
stream.Add(new EventMessage { Body = eventToSave });
stream.CommitChanges(aggregateId);

イベントを再度読み取るには、ストリームを開き、コミットされたイベントのコレクションをループ処理します。

また、Event Store (geteventstore.com、英語) は、プレーンな HTTP に API を、イベント ストリームに .NET を提供します。ES 用語では、「集計」はストア内のストリームと同等です。イベント ストリームでは 3 つの基本的な操作を実行でき、1 つ目が「イベントの書き込み」、2 つ目が「最後のイベント、特定のイベント、複数の中の 1 つのイベントの読み取り」、そして 3 つ目が「更新を取得するためのサブスクライブ」です。

サブスクリプションには 3 種類あります。1 つ目が揮発的なサブスクリプションで、特定のストリームにイベントを書き込むたびに、コールバック関数が呼び出されます。2 つ目が「キャッチアップ」サブスクリプションで、ストア内の各イベントについての通知を取得します。最初は特定のイベントから開始して、その後は、新しく追加されたイベントについて通知されます。3 つ目が、永続的なサブスクリプションです。これは、処理するイベントを複数のコンシューマーが待機しているシナリオに対処します。このサブスクリプションによって、少なくとも 1 回 (おそらくは複数回、予測できない順序で)、イベントがコンシューマーに配信されることが保証されます。

まとめ

イベント ソーシングは、アプリケーションのデータ ソースとしてイベントを使用します。このアプリケーションは、前回正常起動時のエンティティ状態ではなく、関連するビジネス イベントのリストを保存するように構築します。イベント データ ソースは、抽象化のレベルを下げてデータを格納します。そこから、トランザクションとクエリに必要な実際のエンティティ状態に到達するためには、プロジェクションを適用する必要があります。プロジェクションとは、イベントを再生していくつかのタスクを実行する処理のことです。最もわかりやすいプロジェクションの例が、現在状態の構築です。プロジェクションは、イベントからの任意の種類の情報を、任意の数だけ保持できます。


Dino Espositoは、『Microsoft .NET: Architecting Applications for the Enterprise』(Microsoft Press、2014 年) および『Programming ASP.NET MVC 5』(Microsoft Press、2014 年) の共著者です。JetBrains の Microsoft .NET Framework および Android プラットフォームのテクニカル エバンジェリストでもあります。世界各国で開催される業界のイベントで頻繁に講演しており、software2cents.wordpress.com (英語) や Twitter (twitter.com/despos、英語) でソフトウェアに関するビジョンを紹介しています。

この記事のレビューに協力してくれた技術スタッフの Jon Arne Saeteras に心より感謝いたします。