イベントソーシングは、データを上書きするのをやめることを求めます。従来のCRUDアプリケーションでは、ユーザーの配送先住所を更新するということは、該当する行を見つけ、値を変更し、以前の状態を破棄することを意味します。イベントソーシングは異なるアプローチを取ります。それは、ユーザーがアカウントを作成した、住所を更新した、メールアドレスを認証した、といった各変更を「不変の事実(immutable fact)」として保存します。システムの現在の状態は直接保存されるのではなく、これらのイベントを順番に再生(リプレイ)することによって算出されます。

このパターンは現実的な問題を解決します。監査トレイル(監査証跡)は、副産物として自然に得られます。過去の任意の時点における注文の状態を再構築できます。何が起きたのかを正確に再現することで、デバッグが可能です。トレードオフは複雑さです。単純な行の代わりに、事実のストリーム、リードモデル、そして結果整合性を管理することになります。

PostgreSQLをイベントストアとして利用できます。ほとんどのチームがすでに運用しているはずです。PostgreSQLはACIDトランザクション、柔軟なペイロードのためのJSONB、そして実績のあるバックアップツールを提供します。初日からKafkaやCassandra、あるいは専用のイベントストア用データベースを導入する必要はありません。標準的なPostgresインスタンスがあれば、インフラの規模を拡大することなく、イベントソーシングが要求するトランザクションの保証と監査トレイルを得ることができます。

Postgresイベントストアの構成

スキーマは、驚くほどシンプルに設計できます。最低限、イベントを追記(append)するだけで、既存のデータを直接更新(update in place)しないテーブルが必要です。実用的な設計は以下の通りです。

  • id: bigserialまたはUUID。グローバルな順序付けに使用。
  • stream_id: 単一のユーザーや注文に関するすべての変更など、関連するイベントをグループ化するために使用。
  • event_type: テキスト形式(例: UserEmailChanged, PaymentReceived, InventoryAdjusted)。
  • payload: JSONB形式。その事象に関する具体的なデータを保持。
  • occurred_at: タイムゾーン付きの精度。
  • version: ストリームごとのバージョン。楽観的並行性を強制するために使用。

不変性のルールは、アプリケーションコードまたはデータベースの制約によって強制します。(stream_id, version) にユニークインデックスを貼ることで、2つの書き込みプロセスが同じシーケンス番号を追記することを防ぎます。コマンドが届いたら、そのストリームの現在のバージョンを読み取り、インクリメントし、トランザクション内で新しいイベントを挿入します。もし他のプロセスに先を越された場合、ユニーク制約によってエラーが発生するため、リトライするかコマンドを拒否します。

具体的な例を考えてみましょう。在庫管理システムを運用しているとします。quantity カラムを持つ単一の inventory 行の代わりに、inventory_events テーブルにイベントを追記していきます。ItemReceived は10個を追加し、ItemReserved は2個を減らし、ItemShipped は3個を減らします。SKU-42 の現在の在庫を知るには、関連するイベントのペイロードを合計します。3日前の在庫を知るには、そのタイムスタンプまでのイベントのみを合計します。もし先週の火曜日に配送ロジックのバグでエラーが発生したとしても、修正したコードでイベントを再生すれば、真の状態を取得できます。単純な UPDATE 文では、このようなことはできません。

トラブルを避けるための原則

Postgres上で構築する場合でも、規律が必要であることに変わりはありません。以下の原則は、イベントソーシング・システムに直接適用されます。

シンプルに保つ。 複雑さは信頼性を損ないます。実際に動作するフローを一つリリースする前に、汎用的なイベントフレームワークを作りたくなる衝動を抑えてください。単一のテーブル、イベントを追記するためのリポジトリ関数、そしてリードモデルを構築するためのプロジェクションワーカーがあれば、価値を証明するには十分です。具体的な問題が発生したときにのみ、ツールを追加してください。

小さく始める。 モノリス全体を書き換えてはいけません。監査トレイルのメリットがオーバーヘッドを上回るような、一つの境界づけられたコンテキスト(bounded context)を選んでください。請求台帳、ワークフローエンジン、あるいは在庫予約システムなどが良い候補です。その一つのパイプラインをエンドツーエンドで構築し、本番環境で稼働させます。それから、拡張するかどうかを判断してください。

まず成功の定義を行う。 イベントソーシングはデフォルトのアーキテクチャではなく、特定のニーズに対する解決策です。もし要件が最新の状態を追跡することだけであれば、CRUDの方が高速で安価です。もし時間軸に基づいたクエリ(temporal queries)、厳格な監査可能性、あるいはオンデマンドでリードモデルを再構築する能力が必要であれば、イベントを採用する意味があります。導入を決める前に、どの問題を解決しようとしているのかを明確にしてください。

最適化する前に計測する。 控えめなスペックのハードウェアであっても、最新のPostgreSQLなら単純な追記専用テーブルで毎秒数千件のイベントを取り込むことができます。モニタリングによって、より単純な修正策をすべて使い果たしたことが証明されるまでは、イベントストアのシャッディングや複雑なパーティショニングスキームを導入しないでください。クエリするフィールドにインデックスを貼ります。追記専用のワークロードに合わせて autovacuum をチューニングします。その上で、再度計測してください。

すべてをテストしてください。 イベントハンドラーのユニットテストを行いましょう。アペンド(追加)パスの統合テストを行いましょう。最も重要なのは、失敗シナリオをテストすることです。2つのノードが同時に同じストリームにアペンドした場合、何が起こりますか?プロジェクションワーカーがバッチ処理の途中でクラッシュした場合、どうなりますか?楽観的並行性と「少なくとも1回の配信(at-least-once delivery)」の保証を検証するテストを書いてください。

本番環境でモニタリングしましょう。 イベントテーブルは肥大化していきます。更新によって行数が一定に保たれる正規化されたスキーマとは異なり、イベントソーシングは意図的に「追加型(additive)」です。テーブルサイズ、ディスクI/O、そして書き込みモデルと読み取りモデルのプロジェクション間のラグを追跡してください。ユーザーが古いデータに気づく前に、プロジェクションのラグに対してアラートを設定しましょう。

手作業を自動化しましょう。 手動のスキーマ変更、手動のプロジェクション再構築、手動のイベントリプレイは、時限爆弾のようなものです。マイグレーション戦略をスクリプト化してください。イベントスキーマを進化させる場合は、アップキャスティング(upcasting)や変換を自動化し、真夜中に人間が介入することなく、古いイベントを新しいロジックでリプレイできるようにしてください。

選択した理由を文書化しましょう。 なぜ特定のストリームが存在するのか、各イベントタイプが何を意味するのか、そしてチームがイベントではなくCRUDを選択すべき時はいつなのかを書き留めてください。イベントソーシングは認知負荷を高めます。優れたドキュメントがあれば、新しいエンジニアが推測で間違った判断を下し、重要なストリームに不正な形式のイベントをアペンドしてしまうのを防げます。

数ヶ月を無駄にする罠

イベントソーシングは、図解するとエレガントに聞こえますが、本番環境では苦痛を伴うことがあります。以下の罠に注意してください。

複雑さの過小評価。 状態を再構築するためにイベントをリプレイすることは、概念的には単純です。しかし、冪等性(idempotency)の管理、パフォーマンスのためのスナップショット作成、アグリゲートをまたぐ補償トランザクションの管理は、そう単純ではありません。システムを小さな断片に分割しましょう。一度に一つのストリームから解決していくのです。

オーバーエンジニアリング。 「いつかイベント量が増えるだろう」という想像だけで、マルチノードのKafkaクラスターを準備しないでください。Postgresだけでも驚くほど多くのケースに対応できます。新しいインフラを導入するのは、現在の構成では解決できない明確なボトルネックが測定された時だけにしてください。

技術的負債の無視。 古いイベントスキーマは永遠に残り続けます。OrderCreated のペイロードを変更しても、過去の1,000万件のイベントは古い形式のままです。この負債を追跡してください。後方互換性のあるリーダーやマイグレーションスクリプトを計画しましょう。レガシーイベントの負担によって、新しい機能の開発が遅れることがないようにしてください。

チームが運用できないツールを選択すること。 最良のアーキテクチャであっても、それを理解できるのが一人だけなら失敗します。チームがPostgresとSQLに精通しているなら、そこから始めましょう。もし特化したイベントストアを導入するなら、午前2時にデバッグできるだけの運用スキルを備えていることを確認してください。

実践的なスタート地点

もしこのアプローチがあなたの課題に適しているなら、5四半期後のリライトを待つ必要はありません。今週から始めましょう。

現在のシステムを監査してください。監査トレイル(audit trail)があれば真の課題を解決できる場所を見つけましょう。例えば、現在は単一の status カラムだけで管理されている注文ステートマシンかもしれません。あるいは、残高修正のために手動のデータベースパッチが必要な財務台帳かもしれません。状態の書き換えによって問題が発生した箇所を一つ選んでください。

次に、今日できる小さな改善を一つ選びます。一つのイベントテーブルを作成します。一つのストリームをモデル化します。それらのイベントから読み取りモデルを構築する一つのプロジェクションを書きます。それをフィーチャーフラグ(feature flag)の背後でデプロイします。実際のトラフィックを処理する様子を見守りましょう。

PostgreSQLによるイベントソーシングは魔法ではありません。物事が「今どこにあるか」だけでなく、「どのようにしてそこに至ったか」を知る必要があるチームのための、実用的なツールです。ゆっくりと構築し、正直に測定し、実際の要件に基づいてアーキテクチャを導いてください。

Source: https://dev.to/therizwansaleem/event-sourcing-with-postgresql-using-the-database-as-an-event-store-2kd4