イベントソーシングでAggregate Rootを再構築するようなアーキテクチャだと、イベントの進化(Evolving events)がどうしても問題になってくる。Domain Eventの属性が増減したり、リファクタリングでリネームされたり等。
イベントソーシングでは、イベントストアは不変ということで、一度登録したイベントは書き換えない1。Event Upgraderなどをアプリ側に組み込んで、イベントを最新のバージョンに上げていくような実装になってくる。
前提として、イベントが最新かどうかを判断するためには、イベントのバージョンもイベントストアに記録されてないといけない。
真っ先に思いついたのが、Domain Eventの属性に持たせてしまうというもの。
case class AccountCreated(
userId: UserId,
username: Username,
password: Password,
occurredOn: Date,
occurredBy: UserId,
eventVersion: Int = 1
) extends DomainEvent
これでもいいのだけれど、
- Aggregate RootでDomain Eventを作るときに、うっかり
eventVersion
を上書きしてしまう危険性がある - ドメインモデルの属性と、インフラレイヤーの属性が混ざってしまい好ましくない
- イベントバージョンの指定漏れがあった
といった課題があった。
こういった課題を解決するためのアイディアとして、イベントバージョンを型で表現する方法を検討している。
// define your own Domain Event common interface
sealed trait EventVersion
trait Version1 extends EventVersion
trait Version2 extends EventVersion
trait DomainEvent {
val occurredOn: Date
val occurredBy: UserId
}
// Domain Event
case class AccountCreated(
userId: UserId,
username: Username,
password: Password,
occurredOn: Date,
occurredBy: UserId
) extends DomainEvent with Version1
// Value Objects
case class UserId(id: Int) extends AnyVal
case class Username(username: String) extends AnyVal
case class Password(password: String) extends AnyVal
Domain Eventインスタンスのイベントバージョン確認方法
val event = AccountCreated(
UserId(91),
Username("alice"),
Password("p@ssW0rd"),
new Date,
UserId(1)
)
def getEventVersion(event: EventVersion) = event match {
case _: Version1 => "v1"
case _: Version2 => "v2"
}
println(getEventVersion(event)) // v1
-
変えずにこしたことはないが、業務上やむを得ないときはイベントストアを改ざんするのもありだというのが個人的な考え。 ↩