リレーショナルデータからイベントへの移行
(event-driven.io)- リレーショナルな CRUD モデルは保存構造はよく示す一方で、ビジネスプロセスを上書きしやすく、システム内で実際に何が起きたのかを追跡しにくい
- Event Sourcing は各操作の後に生じた不変のイベントを Event Stream として残し、その後の意思決定ではその一覧を読んで現在の状態を判断する
- モデリングはまずイベントを見つけ、続いてユーザーの行動意図であるコマンド(command) とビジネスルールを結び付けてプロセスを理解する流れで進む
- 既存のリレーショナルデータからイベント候補を探すときは、状態カラム、日付カラム、nullable かどうか、1:N 関係を見るが、状態値だけで完全な履歴を復元できると考えるのは危険
- 最終状態だけが残ったデータを移すときは、過去イベントを無理に再構成するより、Order Imported のような明示的な import イベントから始め、安全な環境で繰り返し検証するほうが現実的
CRUD データをイベント中心モデルとして見る
- リレーショナルデータモデルはどのデータを保管しているかは示すが、システム内で何が起きたのかや、プロセスがどのように相互作用しているかは把握しにくい
- 従来の CRUD 方式はデータを上書きしながら重要なビジネス情報を失うことがある
- Event Sourcing は保存サイズより情報の質を優先し、各操作後に発生した事実をイベントとして保存する
Event Sourcing の基本モデル
- イベントはすでに起きたことに関する事実であり、一度保存されたら変更できない不変データである
- Event Stream はひとつのレコードに対して起きたすべての出来事を順番に並べた一覧である
- 過去イベントは修正できないが、最後に新しいイベントを追加することで以前の誤りを正せる
- 意思決定時にはイベント一覧を読み、確認し、現在の状態と次の行動を判断する
プロセスモデリングの順序
- モデリングはまずイベントの発見から始まる
- 次にコマンド(command)を見つけ、どの行動を実行しようとする意図なのかを定義する
- 最後にビジネスルールを整理する
- イベントは技術担当者とビジネス担当者がプロセスを一緒に理解するための中心軸となる
- Alberto Brandolini の EventStorming のように、イベント、コマンド、ルールを一緒に見ながらプロセスを把握できる
既存のリレーショナルデータからイベント候補を探す
-
1. 状態カラムを確認する
- status カラムの値は、データのライフサイクル段階を反映している可能性がある
- 注文が initiated、shipped、paid のような状態を持つなら、それぞれ Order Initiated、Order Shipped、Order Paid のイベント候補になりうる
- ただし状態値はビジネスプロセスを平坦化した解釈かもしれないため、完全だと仮定してはならない
- イベント名を Order Created、Order Updated、Order Deleted のように CRUD 操作で付けるのは避けるべき
- State Obsession は避けるべきやり方として示されている
-
2. 日付カラムを確認する
- 日付カラムはプロセスのライフサイクルにおける重要な発生時点を示してくれることがある
- CreatedDate と ModifiedDate は多くの情報を与えないが、ShipmentDate、DeliveryDate、OrderPlacementDate はより良い手がかりになる
- 例:
- ShipmentDate は Order Shipped イベント導入の手がかりになりうる
- OrderPlacementDate は Order Initiated より Order Placed のほうがよい名前かもしれないことを示唆する
- DeliveryDate は Order Delivered イベントが必要かもしれないことを示す
- こうした手がかりはドメインエキスパートと確認し、実際のビジネスプロセスに合わせる必要がある
-
3. カラムの nullable かどうかを分析する
- non-nullable カラムは常に提供されるべきデータである
- nullable カラムは後続の別の操作で提供されるか、選択値である可能性がある
- Ordering Process における必須カラムなら、そのデータは最初の Order Initiated イベントにも含まれるべきである
- 単一のイベントタイプが常にストリームの開始点になるわけではなく、開始イベントは複数ありうる
-
4. 1:N 関係が多いテーブルを探す
- ストリーム境界を見つけるには、1:N 関係を多く持つテーブルから見ていける
- 多くの「one」関係を持つテーブルはストリームタイプ候補になる
- データが互いに独立して存在できるかどうかも論理的に判断する必要がある
- shipment は order とは別プロセスかもしれない
- order line は order なしでは存在しにくい
- 境界を議論する過程で、より多くのイベントを発見し、プロセス理解を広げられる
移行時に偽のイベントを作らない
- リレーショナルデータは平坦化された最終状態なので、その状態だけを見て実際に起きた詳細な過去イベントを推測しようとすると、失敗したり不正確になったりしやすい
- 細かな過去イベントを無理に作るより、現在状態全体と解釈コードを含む Order Imported イベントを明示的に提供するほうがよい
- import イベントはデータがどのように入ってきたかを明確に示し、問題解決や診断において重要になりうる
プロトタイプで検証する
- 移行は安全な環境でプロトタイプとして試し、モデルが実際にどう動くかを確認する必要がある
- 結果を期待値と比較しながら反復的に修正していくべきである
- 急がず、既存情報を失わないまま、その情報を基に以後のモデルを改善していくやり方が必要である
- リレーショナルデータからドキュメントベースへ移す一般戦略は General strategy for migrating relational data to document-based ともつながる
1件のコメント
Hacker News のコメント
2c: アプリの他の部分でも PostgreSQL が必要なら、イベントデータも PostgreSQL + FOSS のレポーティングツール(Apache Superset、Metabase など)に保存し、おおよそ 2TB まではそれで耐えるのがよい
その後は、2TB 全体をオンラインに保つ必要があるのか、それとも日次/時間単位のサマリーだけあればよいのかを判断すればよい。後者なら PostgreSQL のままでも十分[1]
ある顧客は 10TB+ 規模で毎秒 1,500 イベント、レコードあたり 600 バイト(インデックス作成前で 1 日 80GB)を処理し、詳細データは 2 日分だけオンラインに置き、残りは要約して詳細は S3 に移し、Athena SQL で引き続き照会している[2]
顧客向けレポーティングポータルまで含めた総コストは 2,000 ドル未満で、AWS RDS のマルチ AZ 自動フェイルオーバー(db.m7g.2xlarge)上で挿入とレポーティングクエリの両方を処理しているが、負荷は 2% 未満。ビジネスチームが自分でチャートやグラフを作るため、エンジニア 1 人が保守に使う時間は月 5 時間未満
プロプライエタリなツールを使えば、いくつかのチャートが「標準搭載」されてはいるが、pgsql を使えばデータは 1 か所にあり、学ぶべきシステムも 1 つ、オンライン維持/レプリケーション/バックアップ/リカバリするシステムも 1 つ、セキュリティ/スケーリングするシステムも 1 つ、管理するベンダーも 1 社で、このシステムを知っているエンジニアは何百万人もいる
Preset や Metabase のようなシステムなら、12 個のチャートを作るのに 1 時間あればよく、非技術系の人でもできる
ちなみに偏りはあるが、20 年以上にわたってデータベースとレポーティングシステムが生まれては消えていくのを見てきたし、古き良き PostgreSQL は毎年良くなり続けている
https://instances.vantage.sh/aws/rds/db.m7g.2xlarge?region=u...
[1] 本当に必要なら、さらなるスケールアウトのための PostgreSQL 互換システムもある。Aurora は 3〜5 倍、TimescaleDB は 10 倍、CitusDB は 10 倍超くらいまで拡張可能。それぞれ多少非標準になる代償があるので、実際に必要になるまでは勧めない
[2] 顧客向けレポーティングダッシュボードには 1 秒未満の応答が必要で、これは PostgreSQL がインデックス付きのサマリーテーブルを参照して提供している。Athena は並列スキャンで 1〜2 秒程度で応答する
保存前データのスナップショットを保持し、特定のイベント順序を識別・収集するスクリプトを用意したうえで、人が確認し、新しいロジックの効果を大量に遡及適用すればよい
https://django-simple-history.readthedocs.io/en/latest/ のようなツールは、監査テーブルを作るための、ある程度信頼できるシンプルな解決策であり、データベースへの直接アクセスまで監査する必要があるなら Postgres のトリガーを追加することもできる
理論的にはイベントソーシングは好きだが、実際には新しい CRUD フローを追加したり、初期〜中期のスタートアップが予期しない状況で頻繁に必要とする介入やホットフィックスを素早く安定してデプロイしたりするのに必要なボイラープレートが多すぎる
決済処理レールを実装するような仕事でないなら、イベントソーシングは適切な選択ではないかもしれない
https://news.ycombinator.com/item?id=17817375(2018)にも、イベントソーシングの欠点についての良い議論がある
PostgreSQL の唯一の問題は、挿入側に興味深いスケーラビリティ問題があること。通常はイベントソースと DB の間にキューを置くことを勧める
{id:uuid,created_at:timestamptz,data:jsonb}という定義のテーブルを置くような形なのか気になる特にイベント構造が多様で、イベント定義が変化していく場合、JSONB インデックス機能を十分に活用するのは難しい
おそらくこのドキュメントをもっと理解する必要がありそう: https://www.postgresql.org/docs/current/datatype-json.html#J...
以前、チームでイベントソーシングを本格的に検討したことがあるが、私には問題を探している解決策のように見えた。
私たちにも機能した可能性はあっただろうが、利点がすぐには明確でなく、新しい方式を導入することで得るリスクや試行錯誤が、プロジェクトや会社にとって最善には見えなかったため、結局断念した。
学習機会を逃したツールのような判断だったのかもしれないが、後ろからキツネに追われている状況でもないのに、そのウサギ穴に入らなかったことは後悔していない。
この解決策が解く「問題」はまさにそれである。
しかしほとんどの場合、通常のデータベースを使い、過去の変更履歴を補助テーブルに保存すればよい。そうすればメインデータベースは一種のマテリアライズドビューのように動作する。
大きな不満はないし、間違った選択だったとまでは思わないが、データモデルの変更を扱う方法では問題が生じる。
最近のソフトウェアの作られ方に比べて、ほとんどのデータ保存方式が追いついていないように思えるし、イベントやキューのようなものは、必要な機能を既存システムの上に載せて作った結果である。
今日、多くのデータ関係は複数のサービス間、つまりデータベースの外で成立している。多くの組織の現代的なIT環境がそういう姿をしているためだ。
ビジネスの複数チームを支える内部マスターデータがあり、業務を簡素化するために300以上のITシステムやアプリケーションと相互作用している。
マイクロサービスを使えばビジネスロジックとデータモデルをきれいに保つのは容易だが、その代わりにイベント、キュー、データ状態、依存するストアまで管理しなければならず、今はそれがあまりに複雑である。
SQLは好きだが、正直なところ、今私たちが作っているシステムはSQLiteに入れてもほぼ十分な気がする。
この種の議論で抜け落ちているのは、イベント駆動アーキテクチャがいつ適切なのかという点である。
簡単に言えば、顧客が何かをして応答を期待しているなら、それはイベント駆動ではなく、単なるリクエスト/レスポンスである。
イベント駆動とは、帯域外で何かが起きる場合のことだ。例えばGitHubにコードをpushしてビルドがトリガーされる状況である。
この例で、更新されたコードを見るためにページを再読み込みするのはリクエスト/レスポンスだが、キューに入ったCIビルドはイベント駆動である。
参考になれば幸いである。
イベントソーシングやイベント駆動でも、リクエスト-レスポンス、インライン、ブロッキング、循環的なフローを作ることはできる。
逆に、イベントソーシングやイベント駆動なしでも、ワーカー、キュー、アクター、マルチスレッディングといった方式で非同期をうまく作ることはできる。
ドメインイベントをモデリングすることは、ドメイン専門家と解こうとしている問題を説明するのに有用であり、ソリューションを計画する際にドキュメントとして残しておく程度が適切かもしれない。
長期間持続するステートマシンの監査証跡を提供するシステムを実際に実装するなら、Temporal.ioやdurable functionsのようなものを使うほうがよい可能性が高い。
こうしたツールは内部の永続化にイベントソーシングを使用し、機能をオーケストレーションするコード(workflows)と現実世界と相互作用するコード(activities)に異なる制約を加えることで、重複排除と冪等性を考えるよう強制するプログラミングモデルを提供する。
この問題を乗り越える方法について提案を聞きたい。
概念は興味深いが、記事はその仕組みをうまく説明できていない
イベントストリームから現在の状態をどう効率よく再構築するのか、イベントストリームをデータベースでどうモデル化するのかが気になる
https://www.youtube.com/watch?v=gG6DGmYKk4I
https://www.youtube.com/watch?v=jnDchr5eabI
https://www.youtube.com/watch?v=ArcypYS5XBQ
https://www.youtube.com/watch?v=uODSwR2CIV4
GitHubでもサンプルを管理している
https://github.com/oskardudycz/EventSourcing.NetCore
https://github.com/oskardudycz/EventSourcing.NodeJS
https://github.com/oskardudycz/EventSourcing.JVM
1つ目は、こうした用途向けに設計されたデータベースを使うこと。Google BigQuery、Amazon Redshift、ClickHouseなどがある
現在のデータはすべて、本質的には一種の集計だ。言い換えれば、イベントデータベースに対するgroup-byクエリのようなもの
イベントがあれば、集計クエリで現在の状態や過去の状態を技術的に再現できるので筋は通っている
2つ目は、リレーショナルストアの名前を変えて、イベントシステムの横にあるキャッシュ層と呼ぶこと
機能的には同じものだが、すべてをイベント駆動にしなければならないとこだわる人たちの警戒心を刺激しない
記事で描かれているアーキテクチャは実在する。ただし非常に複雑なので、それを活用するサービスはたいてい非常に絞り込まれたことをしている。Google Analytics、Datadog、Splunkのようなものを思い浮かべればいい
異なる要件に応じて、異なるシステムに異なる状態を作ればよい
ショッピングシステムを作るとすると、購入と顧客がある場合、あるサービスはイベントを読んで財務目的のリレーショナルテーブルを作れる
別のサービスはイベントを読んで顧客データのキー・バリューストアを作り、3つ目のサービスは商品検索用のOpenSearchサービスを動かせる
イベントストリームはリストだ。Kafkaのように目的に合ったものを使うなら、複数のリスト、つまりトピックやパーティションなどになる
ただし、それもリレーショナルモデルの中で解決できる
これはトップダウン vs ボトムアップ、あるいはカスタムメイド vs 汎用の違いだ
トップダウンはビジネスドメインから出発し、利用可能な技術・ツール・ベンダーの上に実装をマッピングすること
ボトムアップは利用可能な技術・ツール・ベンダーから出発し、それらを組み合わせて動く解決策を作る方式
カスタムメイドにはDDD、CQRS/ES、Sagas、TBUI(タスクベース/タスク駆動UI)、GraphQL、代数的データ型などがある
汎用にはRDBMS、CRUD、REST、ACIDトランザクション、CDC、汎用管理UI、ノーコード/ローコード、制限付き/汎用型などがある
ただ昔ながらの良いリレーショナルデータを使い続けるつもり
イベントベースアーキテクチャには同意するが、この記事は要点を伝えるのに苦労しているように見える
データの関係とビジネス上の行為の違いに注目したい
行為とビジネス活動の観点で考え始めると、運用向けのリレーショナルデータストアから離れる動きがはるかに明確になる
イベントソーシングには良い性質が多く、興味深くはある
だが、それでも関係は必要ではないのか? だとすれば、その関係はどう実装するのか?
答えが「すべてアプリケーション層のコードに暗黙的に入っている」なら受け入れがたい
それでも関係を問い合わせたり、関係ビューを最新に保ったり、それに近い何かが必要になる
関係が永続化モデルの中核でなくても構わないが、データ層のどこかには実装されている必要がある。ここではその言及が見当たらない
Firestoreにも同じ問題がある。誰もが何らかの形で関係を処理するが、結局スケールしないスパゲッティなアプリケーションコードになる
関数型プログラミングに慣れているなら、イベントストリームを1つの状態へ畳み込むfold操作と本質的に同じだ
過去にイベントソーシングシステムを扱った経験から言うと、明示的に保存されたイベント履歴があるという利点はあるが、複雑さも大きく増える
読み取りモデルを実際にどう生成するか、モデルのバージョンをどう管理するか、読み取りモデルのスナップショットを置くか、といった問題が生じる
私の経験では、このパターンが適用された大半の文脈では、追加の複雑さに見合う価値はなかった
必要なのはコマンドキューだ。コマンドイベントはドメインイベントではない