🐥

CQRS/ES 俺的ベストプラクティス②

に公開

イベントはイミュータブルに保つ

イベントソーシングを利用したシステムでは、「一度イベントストアに保存されたイベントは不変(イミュータブル)であるべき」という原則があります。

これはイベントとは過去の歴史であり、改変してはならないものだからです。

しかし、長期にわたるシステム運用の中で必ずイベントのスキーマ(構造)を変更したくなる場面がやってきます。

  • フィールドの追加・削除
  • 型の変更や形式の変化
  • etc....

では、イベントの構造が途中で変わる場合、CQRS/ESではどのように対処するべきなのでしょうか?

答えは以下の2択です。

  • イミュータブルなイベント設計を取り入れる
  • イベントの変換処理を書く

個人的にはイミュータブルなイベント設計を取り入れることを強くお勧めします

なぜイベントの変換処理はダメなのか?

たとえば、以下のような依存関係のマイクロサービス群があるとします。

この場合、先に C , B, Dのサービスに対してイベントの変換処理を実装してから、 Aでイベントを発行しなくてはならず、管理が恐ろしく大変になります。

また、例えばDというサービスがイベントの変換処理の実装を忘れていたとします。
この時、 Aサービスから発行された最新のイベントのスキーマとDサービスのスキーマが異なるので、
Dサービスではイベントキャッチできなくなりエラーが起こります。

イミュータブルイベント最高

イミュータブルイベントの場合、Aサービスは新しいイベント (入庫したv2)のようなイベントを定義しpublishするだけで済みます。

C, B, Dサービスは購読の準備ができていない場合、キャッチできないだけでエラーにはなりません。

kotlinの例

今回はAxonFrameworkにおいて、イベントのどのように定義するかの例を示します
なお、このやり方は完全に我流ですが多分問題ないと思います。

// イベントを表すクラスを大枠に作るよ。
class ProductCreatedEvent {
    // イベントはこんな感じで内部クラスとして表現するよ。
    data class V1(
        val id: String,
        val brandId: String,
        val name: String,
        val description: String,
        val imageUrl: String?,
        val price: Int,
        val quantity: Int,
    )

    data class V2(
        val id: String,
        val brandId: String,
        // V2になってジャンルIDが追加されたよ。
        val genreId: String,
        val name: String,
        val description: String,
        val imageUrl: String?,
        val price: Int,
        val quantity: Int,
    )
}

Discussion