🧺

はじめてのイベントソーシング

に公開

現在参画しているプロジェクトで一からイベントソーシングを構築する経験を得たため、その学びをまとめます。

前提

  • 今回のプロジェクトに携わるまで、イベントソーシングは未経験で、設計概念も明確に理解できていなかった
  • DDDについては何となくの知識があり、値オブジェクトなどを部分的に取り入れていた程度
    • DDDやイベントソーシングの深い部分には今回触れません
  • Goで実装
  • データストアにはDynamoDBを使用

作ったもの

https://github.com/Hiroshi0900/dynamodb-eventstore-go

簡単に抜粋して説明をすると、以下のようなことを実装しました。

  • イベントテーブルとスナップショットテーブルを用意
  • イベントテーブルは基本的に追加のみで、削除することもない
  • スナップショットテーブルはドメイン集約(=メインのデータ)に対して常に1つになる(初回だけ作成で以降は更新となる)
    • シーケンスを持っているので、スナップショットも複数個を作るようにはできるが、一旦は0を固定で単一とした
  • 少し横着した形とも言えるが、イベントとスナップショットの保存処理は同じ箇所で実施
    • ただし、スナップショットの更新頻度は決まっているため、一定回数はイベントのみが追加されていく
  • ドメイン集約を取得する際(=復元)に根幹データであるスナップショットを取得して、スナップショット以降のイベントの状態を適用することでデータを復元させる

イベントソーシングとは

イベントソーシングは、システムの状態を「イベント(出来事)」の履歴として記録し、その履歴をもとに現在の状態を復元する設計パターンです。

  • 状態を直接保存するのではなく、「何が起きたか(イベント)」を時系列で保存する
  • ドメインイベント、スナップショット、永続化の責務分離が重要
  • スナップショットは履歴管理や復元のために利用し、保存・削除タイミングはアプリケーションの設計方針に依存する
  • 永続化戦略として、イベント保存(PersistEvent)とスナップショット保存(Snapshot)を分離することで責務を明確化できる
  • 楽観的ロック(例:DynamoDBのConditionExpressionでバージョン管理)を用いることで、同時更新の競合を防げる
  • このパターンは、DDD(ドメイン駆動設計)やマイクロサービスアーキテクチャでよく用いられる

対比されるのはステートソーシングです。
ステートソーシングは、オブジェクトの状態を直接保存・管理するアプローチで、CRUD操作によって状態を更新していくのが一般的です。

イベントソーシングの何が良いのか

  • 状態を直接保存せず、イベントの履歴から状態を復元するため、変更履歴や監査証跡が自然と残る
  • ドメインイベントとスナップショット、永続化の責務を分離することで、設計の柔軟性と保守性が向上する
  • DynamoDBのGSIやパーティション設計を活用することで、スケーラビリティとパフォーマンスを両立できる
  • イベントの流れを追うことで、ドメインロジックの動作や業務モデルの理解が深まる

例えば、以下のようなユースケースを叶えたいとします。
例:太郎さんがコンビニで買い物をする
その時にカゴに以下のように商品を出し入れしていく:

  1. おにぎりを追加する
  2. 水を追加する
  3. ホットスナックを追加する
  4. 水をもう一本追加する
  5. ホットスナックを戻す

このとき、ステートソーシングとイベントソーシングでは、内部的な状態の管理方法が異なります。

ステートソーシング

レジの状態を常に最新にしていくため、レジの枠組み(状態)は逐次更新されていきます。
状態の把握には適していますが、「ホットスナックを追加しようとした」という履歴は残りませんし、水やおにぎりが
どの順番で追加されたのかもわかりません。

イベントソーシング

レジの状態そのものは同じであっても、そこに至るまでの履歴が全て記録されているため、
「ホットスナックを一度追加してから削除した」や「水が2本あるのは2回追加されたから」といった
情報がわかります。
これがイベントソーシングの利点の一つです。

ただし、状態の復元ロジックが複雑になるため、すべてのケースに向いているとは限りません。

難しかったところ

  • そもそもdynamoDBの設計が難しかった。
    • RDBとは設計思想が異なることが多く、その理解も必要になった
    • RDBで言うところのレコードがDynamoDBではアイテムに相当し、それには保存の上限が存在する
    • RDBとの比較というより、割と個人で制御周りも含めた設計にして実装をする必要があったように感じた
  • 集約の復元、つまりデータ取得でのロジックがはじめ理解できずに苦しんだ。
    • ステートソーシングではGETでデータを抜き出すだけで良いことが多いが、イベントソーシングではそれはできない
    • スナップショットとイベントを組み合わせて、現在の状態を復元する必要がある。機能の実装がステートソーシングよりも複雑になる
    • 復元の処理フローは以下のようになる
    • スナップショットは名前通りある期間での集約の状態を保存しており、最新状態に復元するためには、集約を保存した時のシーケンス番号より大きいイベントの情報らを充てていくことで状態を戻す

まとめ

DDDを深く理解しようと試みた結果、ライブラリとして切り出す構想まで広がりました。
とはいえ、ライブラリにしてはまだ考慮不足な面もあり、カスタマイズ性の向上など課題も残っています。
一部はやりたい機能を割り切って省いており、参考にした設計の影響も色濃く出ています。

それでも柔軟な設計ができたと感じていますし、DDDとの親和性が高いことも実感できました。
ステートソーシングに比べて実装コストは高めですが、ドメインが複雑な場合や冪等性が求められるケースでは
有力な選択肢になり得ると感じました。

ライブラリとしてはまだ改善余地があるため、今後さらに拡張していきたいと考えています。

参考にさせていただいた情報など

https://zenn.dev/shmi593/articles/56c890962bb807
https://github.com/j5ik2o/event-store-adapter-go
https://www.oreilly.co.jp/books/9784814400737/

Discussion