Tagbangers Blog

Domain Event with Spring

Domain Event とは?

売上というドメインがある場合 、売上ドメインでは様々な出来事がおきます。
「売上ができた」「売上が変更された」「売上が削除された」

ここで、「売上ができた」という出来事は副作用があるかも知れません。
伝票ができるかもしれませんし、在庫が変動することもあるでしょう。

これらの副作用を売上ドメインだけで完結しようとすると、
伝票ドメインや在庫ドメインとの深い関係を持っている状態になります。
我々の世界ではこれを密結合と言ったりします。
疎結合と密結合であれば、疎結合がいいと言うのはなんとなくわかりますね。
マイクロサービスがもてはやされているのもこれが一つのキーワードだと思います。

深い関係を持たずに、副作用を解決(実装)するためには?の一つの解決策が Domain Event を使ったアプローチです!

Spring で 実装例

org.springframework.data.domain.AbstractAggregateRoot を継承したエンティを作成します。
AbstractAggregateRoot は domainEvents という変数と、registerEvent という Domain Evnet の登録処理を持ちます

売上

public class Sales extends AbstractAggregateRoot { // <1>
    
    @Id
    @GeneratedValue
    private Long id;
    private String itemCode;
    private BigDecimal amount;
 
    /**
     * 売上の登録を行います
     */
    public Sales record() { // <2>
       registerEvent(new SalesCreated(this)); 
       return this;
    }
 }

// <1> AbstractAggregateRoot を継承し Domain Event を扱えるようにする

// <2> 売上登録で起きる副作用は記述せず、売上ができたという事実を Domain Event を登録することで表現する

次に、登録された Domain Event を処理するためのハンドラーを定義します。

売上イベントのハンドラー

@Service
public class InventoryService {
    /**
     * 売上作成イベントのハンドラー
     */
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) // <1>
    public void handleSalesCreatedEvent(SalesCreated salesCreated) { // <2>
        // 在庫を減らしたりする処理
    }
} 

// <1> TransactionalEventListener アノテーションにより、副作用となる処理を同一トランザクションで処理する

// <2> Domain Event を引数にとる

実際に処理を記述する場合は以下の用に使用し、副作用を気にすることなく売上を作成することができます。

this.salesRepository.save(sales.record());

まとめ

Domain Event を使用することで

  • 結合度を下げ、疎結合なアプリケーションを作る手助けとなる
  • 副作用を気にせず記述することができる
  • 副作用がアプリケーション内に散らばることを防ぐ
  • 同一トランザクションで処理することができる

のような利点が得られます。

また、モノリシックなアプリケーションをマイクロサービスにしたいという場合においても、疎結合であることは優位であるのでは無いでしょうか。