web-dev-qa-db-ja.com

RDBMSをイベントソーシングストレージとして使用する

RDBMS(SQL Serverなど)を使用してイベントソーシングデータを保存していた場合、スキーマはどのようになりますか?

いくつかのバリエーションが抽象的な意味で語られているのを見ましたが、具体的なものはありません。

たとえば、「製品」エンティティがあり、その製品への変更が価格、コスト、および説明の形式で提供されるとします。私は私がするかどうかについて混乱しています:

  1. 製品のすべてのフィールドを持つ「ProductEvent」テーブルがあります。各変更は、そのテーブルの新しいレコードに加えて、必要に応じて「誰、何、どこ、なぜ、いつ、どのように」を意味します。コスト、価格、または説明が変更されると、製品を表すために追加されたまったく新しい行。
  2. 製品のコスト、価格、および説明を、外部キー関係で製品テーブルに結合された個別のテーブルに保存します。これらのプロパティに変更が生じた場合は、必要に応じてWWWWWHで新しい行を書き込みます。
  3. WWWWWHと、イベントを表すシリアル化されたオブジェクトを「ProductEvent」テーブルに保存します。これは、特定の製品のアプリケーション状態を再構築するために、イベント自体をアプリケーションコードでロード、デシリアル化、再生する必要があることを意味します。

特に、上記のオプション2について心配しています。極端に言えば、製品テーブルはプロパティごとにほぼ1テーブルになります。特定の製品のアプリケーション状態をロードするには、各製品イベントテーブルからその製品のすべてのイベントをロードする必要があります。このテーブル爆発は私に悪臭を放ちます。

私は「それは依存する」と確信しており、単一の「正しい答え」はありませんが、私は容認できるものと完全に容認できないものの感覚を得ようとしています。また、NoSQLがここで役立つことも知っています。集約ルートに対してイベントを保存できます。つまり、オブジェクトを再構築するためのイベントを取得するためのデータベースへの要求は1つだけですが、NoSQL dbは使用しません。ので、私は代替案を探しています。

104
Neil Barnwell

イベントストアは、イベントの特定のフィールドまたはプロパティについて知る必要はありません。そうしないと、モデルを変更するたびにデータベースを移行する必要があります(古き良き状態ベースの永続性と同様)。したがって、オプション1と2はまったくお勧めしません。

以下は、 Ncqrs で使用されているスキーマです。ご覧のとおり、「イベント」テーブルには関連データがCLOB(つまりJSONまたはXML)として保存されます。これは、オプション3に対応します(汎用の「イベント」テーブルが1つだけ必要なため、「ProductEvents」テーブルはありません。Ncqrsでは、集約イベントへのマッピングは、各EventSourceが実際のイベントに対応する「EventSources」集約ルート。)

Table Events:
    Id [uniqueidentifier] NOT NULL,
    TimeStamp [datetime] NOT NULL,

    Name [varchar](max) NOT NULL,
    Version [varchar](max) NOT NULL,

    EventSourceId [uniqueidentifier] NOT NULL,
    Sequence [bigint], 

    Data [nvarchar](max) NOT NULL

Table EventSources:
    Id [uniqueidentifier] NOT NULL, 
    Type [nvarchar](255) NOT NULL, 
    Version [int] NOT NULL

Jonathan OliverのEvent Store implementation のSQL永続化メカニズムは、基本的にBLOBフィールド「Payload」を持つ「Commits」と呼ばれる1つのテーブルで構成されます。これはNcqrsとほぼ同じですが、イベントのプロパティをバイナリ形式でシリアル化するだけです(たとえば、暗号化サポートが追加されます)。

Greg Youngは、 GregのWebサイトで詳細に文書化されている と同様のアプローチを推奨しています。

彼のプロトタイプの「イベント」テーブルのスキーマは次のとおりです。

Table Events
    AggregateId [Guid],
    Data [Blob],
    SequenceNumber [Long],
    Version [Int]
99
Dennis Traub

GitHubプロジェクト CQRS.NET には、いくつかの異なるテクノロジーでEventStoreを実行する方法の具体例がいくつかあります。執筆時点で Linq2SQLを使用したSQLSQLスキーマ に実装があり、 MongoDB に1つ、-に1つがあります。 DocumentDB (Azureを使用している場合はCosmosDB)と EventStore を使用するもの(上記のとおり)。 Azureでは、フラットファイルストレージと非常によく似たテーブルストレージやBlobストレージなどがあります。

ここでの主なポイントは、それらがすべて同じプリンシパル/コントラクトに準拠していることだと思います。それらはすべて単一の場所/コンテナ/テーブルに情報を保存し、メタデータを使用してイベントを別のイベントから識別し、イベント全体を「そのまま」保存します-場合によってはサポート技術でシリアル化されます。したがって、ドキュメントデータベース、リレーショナルデータベース、またはフラットファイルを選択するかどうかに応じて、イベントストアの同じ意図にすべて到達するためのいくつかの異なる方法があります(任意の時点で気が変わって、移行またはサポートが必要な場合に便利です)複数のストレージ技術)。

プロジェクトの開発者として、私たちが行ったいくつかの選択に関する洞察を共有できます。

まず、(整数ではなく一意のUUID/GUIDを使用しても)さまざまな理由でシーケンシャルIDが戦略的な理由で発生することがわかったため、IDを持つだけではキーに対して一意ではなかったため、メインIDキー列をデータ/真に(アプリケーションの意味で)一意のキーを作成するオブジェクトタイプ。保管する必要がないと言う人もいますが、それはグリーンフィールドであるか、既存のシステムと共存する必要があるかによって異なります。

保守性の理由から、単一のコンテナ/テーブル/コレクションに固執しましたが、エンティティ/オブジェクトごとに個別のテーブルで遊んでいました。実際には、アプリケーションが「CREATE」アクセス許可を必要とすることを意味します(一般的に言えば、これは良いアイデアではありません...一般に、例外/除外は常に存在します)、または新しいエンティティ/オブジェクトが存在するか、デプロイされるたびに、新しいストレージコンテナ/テーブル/コレクションを作成する必要がありました。これは、ローカル開発では非常に遅く、本番環境では問題があることがわかりました。そうではないかもしれませんが、それは私たちの実際の経験でした。

覚えておくべきもう1つのことは、アクションXを実行すると、多くの異なるイベントが発生する可能性があり、コマンド/イベント/これまでに有用なものによって生成されたすべてのイベントを把握できることです。また、さまざまなオブジェクトタイプにまたがることもあります。ショッピングカートで「購入」を押すと、アカウントイベントと倉庫イベントが発生します。消費アプリケーションは、これらすべてを知りたい場合があるため、CorrelationIdを追加しました。これは、消費者がリクエストの結果として発生したすべてのイベントを要求できることを意味しました。 schema に表示されます。

特にSQLの場合、インデックスとパーティションが適切に使用されていないと、パフォーマンスが本当にボトルネックになることがわかりました。スナップショットを使用している場合、イベントは逆順でストリーミングする必要があることを忘れないでください。いくつかの異なるインデックスを試してみましたが、実際には、実稼働中の実際のアプリケーションのデバッグには追加のインデックスが必要であることがわかりました。繰り返しますが、 schema で確認できます。

他の実稼働中のメタデータは、実稼働ベースの調査中に役立ちました。タイムスタンプは、イベントが持続するか発生するかの順序についての洞察を与えてくれました。これにより、膨大な量のイベントを発生させる特にイベント駆動型のシステムに関する支援が得られ、ネットワークやネットワーク全体のシステム分散などのパフォーマンスに関する情報が得られました。

7
cdmdotnet

まあ、あなたはダトミックを見てみたいかもしれません。

Datomicは、柔軟な時間ベースのファクトのデータベースであり、クエリと結合をサポートし、弾力的なスケーラビリティを備えています、およびACIDトランザクション。

私は詳細な答えを書いた ここ

Datomicの設計を説明するスチュアートハロウェイの講演を見ることができます here

Datomicはファクトを時間内に保存するため、イベントソースのユースケースなどに使用できます。

3
kisai

考えられるヒントは、デザインの後に「緩やかに変化するディメンション」(type = 2)が続くことです。

  • 発生するイベントの順序(代理キー経由)
  • 各状態の耐久性(有効-有効)

左折機能も実装する必要がありますが、将来のクエリの複雑さを考慮する必要があります。

1

ドメインモデルが進化するにつれて、ソリューション(1と2)は非常に迅速に問題になる可能性があると思います。新しいフィールドが作成され、一部は意味が変わり、一部は使用できなくなります。最終的に、テーブルには何十ものヌル値を許可するフィールドがあり、イベントのロードは混乱します。

また、イベントストアは書き込みにのみ使用する必要があり、集約のプロパティではなく、イベントを読み込むためにクエリするだけであることを忘れないでください。それらは別個のものです(それがCQRSの本質です)。

解決策3人々が通常行うこと、それを達成する多くの方法があります。

たとえば、 EventFlow CQRS SQL Serverで使用すると、このスキーマでテーブルが作成されます。

CREATE TABLE [dbo].[EventFlow](
    [GlobalSequenceNumber] [bigint] IDENTITY(1,1) NOT NULL,
    [BatchId] [uniqueidentifier] NOT NULL,
    [AggregateId] [nvarchar](255) NOT NULL,
    [AggregateName] [nvarchar](255) NOT NULL,
    [Data] [nvarchar](max) NOT NULL,
    [Metadata] [nvarchar](max) NOT NULL,
    [AggregateSequenceNumber] [int] NOT NULL,
 CONSTRAINT [PK_EventFlow] PRIMARY KEY CLUSTERED 
(
    [GlobalSequenceNumber] ASC
)

ここで:

  • GlobalSequenceNumber:単純なグローバル識別。プロジェクション(readmodel)を作成するときに欠落イベントを順序付けまたは識別するために使用できます。
  • BatchId:アトミックに挿入されたイベントのグループの識別(TBH、これがなぜ有用かわからない)
  • AggregateId:集約の識別
  • Data:シリアル化されたイベント
  • メタデータ:イベントからのその他の有用な情報(例:デシリアライズに使用されるイベントタイプ、タイムスタンプ、コマンドからの発信者IDなど)
  • AggregateSequenceNumber:同じアグリゲート内のシーケンス番号(順不同で書き込みを行うことができない場合に役立ちます。したがって、このフィールドを使用して楽観的な同時実行を実現します)

ただし、ゼロから作成する場合は、YAGNIの原則に従い、ユースケースの最小限の必須フィールドで作成することをお勧めします。

0
Fabio Marreco