スプリングデータによるドメインイベントの発行


ドメイン駆動型設計に関する私の最後の2つの記事では、方法と方法を見ました.今、私たちは、ドメインイベントを詳しく見ていきます.
ビューの春の観点から、ドメインイベントはApplicationEventPublisher . 言い換えると、我々はイベントイベントを発行するためにイベントバスまたは他のインフラストラクチャを構築することについて心配する必要はありません.しかし、ほとんどの場合、ドメインのイベントを直接その目的のためにドメインサービスを経由しなければならないことなく、集約から発行したい.幸いにも、我々はそれを行うことができます.
春のデータは、イベントパブリッシャのホールドを取得することなく、集計内のドメインイベントを直接公開するためのメカニズムを提供します.我々はすでに見たときBaseAggregateRoot そして今、我々はそれを詳しく見ていきます.
フードの下で、Spring Bootは名前のような保存を開始するすべてのリポジトリメソッドのメソッドインターセプターを登録しますsave and saveAndFlush . このインターセプターは集合で2つのメソッドを探します@DomainEvents と他の@AfterDomainEventPublication .
このメソッドは@DomainEvents が発行するイベントのリストを返すことが期待される.Interceptorは、これらのイベントをSpring Application Event Publisherを使用して発行します.イベントが発行されると、このメソッドは@AfterDomainEventPublication が呼び出される.このメソッドは、イベントのリストをクリアし、次回の集約が保存されたときに再び公開されるのを防ぐことが期待されます.
このようにしてドメインイベントをデザインして発行する際に留意すべき注意事項があります.
public class PotentiallyProblematicDomainEvent {
    private final MyAggregate myAggregate;

    public PotentiallyProblematicDomainEvent(@NotNull MyAggregate myAggregate) {
        this.myAggregate = myAggregate;
    }

    public @NotNull MyAggregate getMyAggregate() {
        return myAggregate;
    }
}
あなたがこのようなイベントを設計するときはいつでも、あなたはどのようにスプリングデータとJPAがフードの下で働くかについて注意しなければなりません.
既存のエンティティを保存するとき(そしてここではDDD 1ではなくJPAエンティティの概念について話しています)、Spring Dataは呼び出しを終了しますEntityManager.merge . エンティティが分離されている場合、JPAはマネージエンティティを取得し、分離したエンティティからマネージ1にすべての属性をコピーして保存し、それを返します.マネージエンティティは、アタッチされたエンティティが未維持のまま、その楽観的ロックバージョンをインクリメントします.
ただし、ドメインイベントが分離されたエンティティに登録されているため、ドメインイベントリスナーは、分離したエンティティへの参照を取得します.これは、リスナーが直接エンティティ上の任意の操作を実行しようとすると、楽観的なロックエラーにつながる可能性がありますし、保存します.
ここでは、リスナーが不正確な楽観的ロックバージョンを持つ古いエンティティを取得してしまう場合があります.
public class PotentiallyProblematicApplicationService {

    @Transactional
    public void firstProblematicMethod(@NotNull MyAggregate aggregate) { // <1>
        aggregate.performAnOperationThatRegistersAProblematicDomainEvent();
        myAggregateRepository.saveAndFlush(aggregate);
    }

    public void secondProblematicMethod(@NotNull MyAggregateId aggregateId) { // <2>
        var aggregate = myAggregateRepository.getById(aggregateId);
        aggregate.performAnOperationThatRegistersAProblematicDomainEvent();
        myAggregateRepository.saveAndFlush(aggregate);
    }
}
  • このメソッドは、集約をパラメーターとして受け取り、直接操作を行います.これは集約が取り除かれ、保存されると古い状態になります.
  • このメソッドは、集約IDをパラメーターとして受け取り、それを操作する前に集計を調べます.しかし、方法はありません@Transactional これは、両方のリポジトリを呼び出すときに自分自身のトランザクションの中で実行され、集約を分離することを意味します.
  • さて、どうやってこれに対処しますか?第2の方法は非常に簡単です@Transactional . このようにして、集約が保存され、ドメインイベントリスナーが正しいインスタンスを取得したときにも集約されます.
    しかし、最初の方法はどうですか?明らかな解決策は、イベント内の集計自体の代わりに集約IDを使用することです.しかし、これは、それ自身の問題を持ちます:集合が固執される前にイベントが登録されるならば、集合にはIDがありません.しかし、次のように修正することもできます.
    public class SaferDomainEvent { 
        private final MyAggregate myAggregate;
    
        public SaferDomainEvent(@NotNull MyAggregate myAggregate) { // <1>
            this.myAggregate = myAggregate;
        }
    
        public @NotNull MyAggregateId getMyAggregateId() { // <2>
            return myAggregate.getIdentifier();
        }
    }
    
  • 我々はまだイベント内の集計への参照を格納します.
  • ... しかし、我々はそのIDを外部の世界に公開するだけです.そして、どんなリスナーも彼らがそれについて何でもしたいならば、どんな倉庫も倉庫から新しいコピーをフェッチさせます.
  • これは再びあなたのドメインモデルに静かにこっそり浸透する技術の例です.持続性技術の選択はあなたのドメインイベントのデザインにも影響を与えます.幸いにも、このケースでは、それは大きなことではないが、それはまだ戻ってくるかもしれませんが、後であなたの初期のデザインを前提にした後にあなたを噛む可能性があります何かは、後で誤って(私はそこに行ったことがありますし、それが行われたときに、特にそれがJPAに来る).ボトトラインは、あなたのツールをよく知っている必要があります-だけでなく、それらを使用する方法だけでなく、どのように動作します.
    将来のポストでは、我々はどのように我々が公開しているドメインイベントをキャッチする方法を見て、いくつかの警告に関連する.