アプリケーションイベントの操作

アプリケーションモジュールを相互にできるだけ切り離した状態に保つには、それらの主な対話手段はイベントの発行と消費である必要があります。これにより、元のモジュールがすべての潜在的な関係者について知ることがなくなります。これは、アプリケーションモジュールの統合テストを可能にする重要な側面です ( 統合テストアプリケーションモジュールを参照)。

多くの場合、アプリケーションコンポーネントは次のように定義されています。

  • Java

  • Kotlin

@Service
@RequiredArgsConstructor
public class OrderManagement {

  private final InventoryManagement inventory;

  @Transactional
  public void complete(Order order) {

    // State transition on the order aggregate go here

    // Invoke related functionality
    inventory.updateStockFor(order);
  }
}
@Service
class OrderManagement(val inventory: InventoryManagement) {

  @Transactional
  fun complete(order: Order) {
    inventory.updateStockFor(order)
  }
}

complete(…) メソッドは、関連する機能を引き付け、他のアプリケーションモジュールで定義された Spring Bean との相互作用を引き付けるという意味で、関数重力を作成します。これにより、OrderManagement のインスタンスを作成するためだけに Bean に依存するインスタンスを使用できる必要があるため、コンポーネントのテストが特に難しくなります ( 遠心性依存関係への対処を参照)。これは、ビジネスイベントのオーダー完了にさらなる機能を統合したい場合は常に、クラスをタッチする必要があることも意味します。

アプリケーションモジュールの対話を次のように変更できます。

Spring の ApplicationEventPublisher を介したアプリケーションイベントの公開
  • Java

  • Kotlin

@Service
@RequiredArgsConstructor
public class OrderManagement {

  private final ApplicationEventPublisher events;
  private final OrderInternal dependency;

  @Transactional
  public void complete(Order order) {

    // State transition on the order aggregate go here

    events.publishEvent(new OrderCompleted(order.getId()));
  }
}
@Service
class OrderManagement(val events: ApplicationEventPublisher, val dependency: OrderInternal) {

  @Transactional
  fun complete(order: Order) {
    events.publishEvent(OrderCompleted(order.id))
  }
}

他のアプリケーションモジュールの Spring Bean に依存する代わりに、プライマリ集約で状態遷移を完了したら、Spring の ApplicationEventPublisher を使用してドメインイベントを発行する方法に注目してください。イベント発行に対するより集約主導のアプローチの詳細については、Spring Data のアプリケーションイベント公開メカニズムを参照してください。イベント発行は既定で同期的に行われるため、全体的な配置のトランザクションセマンティクスは上記の例と同じままです。良い面としては、非常に単純な一貫性モデル (オーダーのステータス変更在庫更新の両方が成功するか、どちらも成功しないかのいずれか) が得られるという点がありますが、悪い面としては、トリガーされる関連機能が増えるとトランザクション境界が広がり、エラーの原因となっている機能が重要でなくても、トランザクション全体が失敗する可能性があるという点があります。

これにアプローチする別の方法は、トランザクションのコミット時にイベントの消費を非同期処理に移行し、二次的な機能をまったく同じように扱うことです。

非同期のトランザクションイベントリスナー
  • Java

  • Kotlin

@Component
class InventoryManagement {

  @Async
  @TransactionalEventListener
  void on(OrderCompleted event) { /* … */ }
}
@Component
class InventoryManagement {

  @Async
  @TransactionalEventListener
  fun on(event: OrderCompleted) { /* … */ }
}

これにより、元のトランザクションがリスナーの実行から効果的に切り離されるようになりました。これにより、元のビジネストランザクションの拡大は回避されますが、リスクも生じます。何らかの理由でリスナーが失敗すると、各リスナーが実際に独自のセーフティネットを実装しない限り、イベントのパブリケーションが失われます。さらに悪いことに、メソッドが呼び出される前にシステムが失敗する可能性があるため、完全には機能しません。

アプリケーションモジュールリスナー

トランザクション自体でトランザクションイベントリスナーを実行するには、次に @Transactional のアノテーションを付ける必要があります。

トランザクション自体で実行される非同期のトランザクションイベントリスナー
  • Java

  • Kotlin

@Component
class InventoryManagement {

  @Async
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  @TransactionalEventListener
  void on(OrderCompleted event) { /* … */ }
}
@Component
class InventoryManagement {

  @Async
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  @TransactionalEventListener
  fun on(event: OrderCompleted) { /* … */ }
}

イベントを介してモジュールを統合するデフォルトの方法を記述する宣言を容易にするために、Spring Modulith はショートカットとして @ApplicationModuleListener を提供します。

アプリケーションモジュールリスナー
  • Java

  • Kotlin

@Component
class InventoryManagement {

  @ApplicationModuleListener
  void on(OrderCompleted event) { /* … */ }
}
@Component
class InventoryManagement {

  @ApplicationModuleListener
  fun on(event: OrderCompleted) { /* … */ }
}

イベント出版レジストリ

Spring Modulith には、Spring Framework のコアイベント公開メカニズムにフックするイベント公開レジストリが付属しています。イベント公開時に、イベントを受け取るトランザクションイベントリスナーを検出し、それぞれのエントリ (濃い青色) を元のビジネストランザクションの一部としてイベント公開ログに書き込みます。デフォルトでは、@TransactionalEventListener で (メタ) アノテーションが付けられたすべてのイベントリスナーが考慮されます。これをカスタマイズする場合は、 spring.modulith.events.registry-trigger-annotation プロパティを参照してください。

event publication registry start
図 1: 実行前のトランザクションイベントリスナーの配置

各トランザクションイベントリスナーは、リスナーの実行が成功した場合にそのログエントリを完了としてマークするアスペクトにラップされます。リスナーが失敗した場合、ログエントリはそのまま残るため、アプリケーションのニーズに応じて再試行メカニズムをデプロイできます。イベントの自動再公開は、spring.modulith.events.republish-outstanding-events-on-restart プロパティを介して有効にできます。

event publication registry end
図 2: 実行後のトランザクションイベントリスナーの配置

イベント公開ライフサイクル (2.0 以降)

Spring Modulith 2.0 では、イベントパブリケーション専用のライフサイクルが導入され、処理開始予定、現在処理中、完了、失敗したパブリケーションを区別できるようになりました。これにより、処理中のパブリケーションを誤って失敗とみなすことなく、失敗したパブリケーションのみを再送信し、クラッシュから回復することが容易になります。

出版物の状態

各イベントの公開には EventPublication.Status があります。

  • PUBLISHED – 出版物は保存され、処理を待機中です(または取得されようとしています)。

  • PROCESSING – リスナーがパブリケーションを要求し、実行中です。リスナーの周囲のインターセプターは、リスナーを呼び出す前にこの値を設定し、リスナーが戻ったときに COMPLETED または FAILED に設定します。

  • COMPLETED – リスナーは正常に終了しました。完了日が設定されます(完了モードが DELETE の場合を除く)。

  • FAILED – リスナーが例外をスローしたか、パブリケーションが古さのメカニズムによって失敗としてマークされました ( イベント公開の陳腐化と自動失敗マークを参照)。

  • RESUBMITTED – 以前失敗した公開が再送信され、再び処理が保留中です。

event-publication-lifecycle

出版の詳細

ステータスに加えて、各出版物は次のものを追跡します。

  • 最終再提出日– 出版物が最後に再提出された日(再提出された場合)。EventPublication.getLastResubmissionDate() で公開されます。

  • 完了試行回数– リスナーが呼び出された頻度(現在の実行を含む)。PROCESSING に移行するとインクリメントされるため、リスナー実行中にクラッシュが発生しても試行回数は更新されたままになります。EventPublication.getCompletionAttempts() を介して公開されます。

これらを使用すると、再送信 API とオプションを使用して、「X 時間以上失敗した場合のみ再送信する」や「N 回の試行後に停止する」などのポリシーを実装できます。

イベント公開の陳腐化と自動失敗マーク

アプリケーションがクラッシュしたり、リスナーがハングアップしたりした場合、パブリケーションは PUBLISHEDPROCESSINGRESUBMITTED の状態のままになることがあります。これらの状態を失敗として処理し、再送信(または無視)できるように、それぞれの状態がどのくらいの期間経過すると古い状態とみなされるかを設定できます。古いパブリケーションは、バックグラウンドタスクによって定期的に FAILED としてマークされます。

Spring Modulith は (2.0 以降) 設定可能な間隔でスケジュールされたタスクとして実行される、陳腐化モニタを提供します。陳腐化期間のいずれかがゼロ以外の値に設定されている場合、モニタはアクティブになります。各実行時に、対応する期間よりも古いイベント発行を PUBLISHEDPROCESSINGRESUBMITTED で検出し、FAILED としてマークします。これにより、(FailedEventPublications.resubmit(…) などを介して) 回復したり、そうでなければスタックしたままになる発行を他のメソッドで処理したりすることができます。カスタマイズは spring.modulith.events.staleness 構成プロパティで行います。publishedprocessingresubmitted がすべてゼロ (デフォルト) の場合、陳腐化モニタはスケジュールされたタスクを登録せず、失敗として自動的にマークされることもありません。

掲載失敗と再投稿

レジストリを使用すると、失敗した出版物を明示的に操作できます。

  • FailedEventPublications (2.0 以降) – この型の Bean を使用して、不合格となった出版物のみを再提出します: resubmit(ResubmissionOptions)

  • ResubmissionOptions – 再提出の動作を制御します。バッチサイズ、処理中の最大件数、出版物の最小経過日数、オプションのフィルター(イベント型や completionAttempts など)を設定できます。ResubmissionOptions.defaults() で作成し、withBatchSize(…)withMinAge(…)withFilter(…) などでカスタマイズします。

再送信を行うと、ステータスが FAILED から RESUBMITTED に変更され、最終再送信日が更新されます。リスナーが実行しようとすると、パブリケーションは PROCESSING に移行し、完了試行回数が増加します。

一般的に「不完全な」出版物(失敗した出版物や、設定によっては古い出版物を含む)については、既存の IncompleteEventPublications API が引き続き適用されます。2.0 以降では、述語ベースおよび期間ベースのオーバーロードに加えて、resubmitIncompletePublications(ResubmissionOptions) もサポートされています。

Spring Boot イベントレジストリスターター

トランザクションイベント発行ログを使用するには、アプリケーションに追加されたアーティファクトの組み合わせが必要です。その作業を容易にするために、Spring Modulith は、使用する永続化テクノロジを中心に据え、Jackson ベースの EventSerializer 実装をデフォルトとするスターター POM を提供します。次のスターターが利用可能です。

永続化テクノロジー 成果物 説明

JPA

spring-modulith-starter-jpa

永続化テクノロジとして JPA を使用します。

JDBC

spring-modulith-starter-jdbc

永続化テクノロジとして JDBC を使用します。JPA ベースのアプリケーションでも動作しますが、実際のイベントの永続性のために JPA プロバイダーをバイパスします。

MongoDB

spring-modulith-starter-mongodb

MongoDB を永続化テクノロジーとして使用します。また、MongoDB トランザクションを有効にし、対話するサーバーのレプリカセットの設定が必要です。トランザクションの自動構成は、spring.modulith.events.mongodb.transaction-management.enabled プロパティを false に設定することで無効にできます。

Neo4j

spring-modulith-starter-neo4j

Spring Data Neo4j の背後で Neo4j を使用します。

イベント出版物の管理

アプリケーションの実行中に、イベントの発行をさまざまな方法で管理する必要がある場合があります。不完全な発行は、一定時間後に、対応するリスナーに再送信する必要があります。一方、完了した発行は、データベースから削除するか、アーカイブストアに移動する必要があります。このようなハウスキーピングのニーズはアプリケーションごとに大きく異なるため、Spring Modulith では両方の種類の発行を処理する API を提供しています。この API は、アプリケーションに追加できる spring-modulith-events-api アーティファクトを通じて利用できます。

Spring Modulith イベント API アーティファクトの使用
<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-events-api</artifactId>
  <version>2.1.0</version>
</dependency>
dependencies {
  implementation 'org.springframework.modulith:spring-modulith-events-api:2.1.0'
}

この成果物には、アプリケーションコードから Spring Bean として利用できる主要な抽象化が含まれています。

  • CompletedEventPublications — このインターフェースを使用すると、完了したすべてのイベント公開にアクセスでき、それらすべてをデータベースから即座に削除したり、指定された期間 (たとえば、1 分) より古い完了した公開を削除したりするための API が提供されます。

  • IncompleteEventPublications — このインターフェースを使用すると、未完了のイベント公開情報すべてにアクセスして、指定された述語に一致するもの、元の公開日から特定の Duration より古いもの、または resubmitIncompletePublications(ResubmissionOptions) (2.0 以降) を介してカスタム条件に一致するものを再送信できます。

  • FailedEventPublications (2.0 以降) — このインターフェースでは、掲載失敗と再投稿に従って、resubmit(ResubmissionOptions) を介して失敗したイベント公開のみを再送信できます。

イベント公開完了

トランザクションまたは @ApplicationModuleListener 実行が正常に完了すると、イベント発行は完了としてマークされます。デフォルトでは、補完は EventPublication に完了日を設定することによって登録されます。つまり、完了した発行はイベント発行レジストリに残り、前述のように CompletedEventPublications インターフェースを通じてインスペクションできます。この結果、古い完了した EventPublication を定期的に消去するコードを配置する必要があります。そうしないと、リレーショナルデータベーステーブルなどの永続的な抽象化が無制限に大きくなり、新しい EventPublication を作成して完了するストアとのやり取りが遅くなる可能性があります。

Spring Modulith 1.3 では、構成プロパティ spring.modulith.events.completion-mode が導入され、2 つの追加完了モードがサポートされます。デフォルトでは、上記の戦略によってサポートされる UPDATE になります。または、完了モードを DELETE に設定することもできます。これにより、レジストリの永続化メカニズムが変更され、完了時に EventPublication が削除されます。つまり、CompletedEventPublications はパブリケーションを返さなくなりますが、同時に、完了したイベントを永続化ストアから手動で消去する必要がなくなります。

3 番目のオプションは ARCHIVE モードで、エントリをアーカイブテーブル、コレクション、ノードにコピーします。そのアーカイブエントリでは、完了日が設定され、元のエントリは削除されます。DELETE モードとは異なり、完了したイベントの公開には、CompletedEventPublications 抽象化を介して引き続きアクセスできます。

イベント発行リポジトリ

イベント発行ログを実際に書き込むために、Spring Modulith は、トランザクションをサポートする一般的な永続化テクノロジ (JPA、JDBC、MongoDB など) の EventPublicationRepository SPI と実装を公開します。使用する永続化テクノロジを選択するには、対応する JAR を Spring Modulith アプリケーションに追加します。このタスクを容易にするために、専用のスターターを用意しました。

JDBC ベースの実装では、対応する設定プロパティ(spring.modulith.events.jdbc.schema-initialization.enabled)が false に設定されていない限り、イベント発行ログ専用のテーブルが作成されます。必要なテーブルがすでに存在する場合(たとえば、Flyway や Liquibase などのデータベース移行ツールによって作成された場合など)、スキーマの作成は自動的に中止されます。詳細については、付録のスキーマの概要を参照してください。

イベントシリアライザー

各ログエントリには、直列化された形式で元のイベントが含まれています。spring-modulith-events-core に含まれる EventSerializer 抽象化により、イベントインスタンスをデータストアに適した形式に変換するさまざまな戦略をプラグインできます。Spring Modulith は、spring-modulith-events-jackson アーティファクトを通じて Jackson ベースの JSON 実装を提供します。これは、デフォルトで標準の Spring Boot 自動構成を通じて ObjectMapper を消費する JacksonEventSerializer を登録します。

イベント発行日のカスタマイズ

デフォルトでは、イベント発行レジストリは、Clock.systemUTC() によって返された日付をイベント発行日として使用します。これをカスタマイズしたい場合は、アプリケーションコンテキストにクロック型の Bean を登録します。

@Configuration
class MyConfiguration {

  @Bean
  Clock myCustomClock() {
    return … // Your custom Clock instance created here.
  }
}

イベントの外部化

次のセクションでは、非同期イベントリスナーに基づく Spring Modulith ネイティブのイベント外部化について説明します。これは実用的でシンプルなソリューションですが、開発者が実際のアウトボックスパターン実装に期待する重要な機能が欠落しています。Spring Modulith 2.1 では、ナマスタックアウトボックス (英語) および JobRunr (英語) を通じてイベント外部化のサポートが導入されました。詳細については、ドキュメントの該当セクション(ナマスタックJobRunr)を参照してください。

アプリケーションモジュール間で交換されるイベントの中には、外部システムにとって興味深いものもあります。Spring Modulith では、選択したイベントをさまざまなメッセージブローカーに公開できます。このサポートを使用するには、次の手順を実行する必要があります。

  1. ブローカー固有の Spring Modulith アーティファクトをプロジェクトに追加します。

  2. Spring Modulith または jMolecules' @Externalized アノテーションを使用して、外部化するイベント型を選択します。

  3. アノテーションの値にブローカー固有のルーティングターゲットを指定します。

外部化するイベントを選択する他の方法を使用する方法、またはブローカー内でイベントのルーティングをカスタマイズする方法については、イベント外部化の基礎を確認してください。

サポートされているインフラストラクチャ

ブローカ 成果物 説明

Kafka

spring-modulith-events-kafka

ブローカーとのやり取りには Spring Kafka を使用します。論理ルーティングキーは、Kafka のトピックおよびメッセージキーとして使用されます。

AMQP

spring-modulith-events-amqp

互換性のあるブローカーとの対話には Spring AMQP を使用します。たとえば、Spring Rabbit の明示的な依存関係の宣言が必要です。論理ルーティングキーは AMQP ルーティングキーとして使用されます。

JMS

spring-modulith-events-jms

Spring のコア JMS サポートを使用します。ルーティングキーはサポートされません。

Spring メッセージング

spring-modulith-events-messaging

Spring のコア Message および MessageChannel サポートを使用します。Externalized アノテーションの target を与えられた Bean 名でターゲット MessageChannel を解決します。ルーティング情報をヘッダー (springModulith_routingTarget と呼ばれます) として転送し、下流のコンポーネントによって任意の方法で処理します (通常は Spring Integration IntegrationFlow)。

イベント外部化の基礎

Spring Modulith のイベント外部化は、ブローカー固有のパブリケーション実装に委譲するトランザクションイベントリスナーとして実装されています。つまり、Spring Modulith のイベント公開レジストリは、ブローカーとのやり取り中に発生した障害から外部化を保護し、提供された API を通じてパブリケーションを再送信できるようにします。

イベントの外部化では、発行された各アプリケーションイベントに対して 3 つのステップが実行されます。

  1. イベントを外部化する必要があるかどうかの決定 — これを「イベント選択」と呼びます。デフォルトでは、Spring Boot 自動設定パッケージ内にあり、サポートされている @Externalized アノテーションのいずれかでアノテーションが付けられているイベント型のみが外部化用に選択されます。

  2. メッセージの準備 (オプション) — デフォルトでは、イベントは対応するブローカーインフラストラクチャによってそのまま直列化されます。オプションのマッピングステップを使用すると、開発者は元のイベントをカスタマイズしたり、外部のパーティに適したペイロードに完全に置き換えたりすることができます。Kafka および AMQP の場合、開発者は公開するメッセージにヘッダーを追加することもできます。

  3. ルーティングターゲットの決定  — メッセージブローカークライアントには、メッセージを公開するための論理ターゲットが必要です。ターゲットは通常、物理インフラストラクチャ (ブローカーに応じてトピック、エクスチェンジ、キュー) を識別し、多くの場合、イベント型から静的に派生します。@Externalized アノテーションで明示的に定義されていない限り、Spring Modulith はアプリケーションローカル型名をターゲットとして使用します。つまり、ベースパッケージが com.acme.app である Spring Boot アプリケーションでは、イベント型 com.acme.app.sample.SampleEvent は sample.SampleEvent に公開されます。

    一部のブローカーでは、実際のターゲット内でさまざまな目的に使用される、かなり動的なルーティングキーを定義することもできます。デフォルトでは、ルーティングキーは使用されません。

アノテーションベースのイベント外部化構成

@Externalized アノテーションを介してカスタムルーティングキーを定義するには、各特定のアノテーションで使用可能なターゲット / 値属性に $target::$key のパターンを使用できます。ターゲットとキーは両方とも、ルートオブジェクトとして構成されたイベントインスタンスを取得する SpEL 式にすることができます。

SpEL 式によるダイナミックルーティングキーの定義
  • Java

  • Kotlin

@Externalized("customer-created::#{#this.getLastname()}") (2)
class CustomerCreated {

  String getLastname() { (1)
    // …
  }
}
@Externalized("customer-created::#{#this.getLastname()}") (2)
class CustomerCreated {
  fun getLastname(): String { (1)
    // …
  }
}

CustomerCreated イベントは、アクセサーメソッドを介して顧客の姓を公開します。その後、そのメソッドは、ターゲット宣言の :: 区切り文字に続くキー式の #this.getLastname() 式を介して使用されます。

キーの計算がより複雑になる場合は、それをイベントを引数として受け取る Spring Bean に委譲することをお勧めします。

Spring Bean を呼び出してルーティングキーを計算する
  • Java

  • Kotlin

@Externalized("…::#{@beanName.someMethod(#this)}")
@Externalized("…::#{@beanName.someMethod(#this)}")

プログラムによるイベント外部化構成

spring-modulith-events-api アーティファクトには、開発者が上記のすべての手順をカスタマイズできる EventExternalizationConfiguration が含まれています。

イベントの外部化をプログラムで構成する
  • Java

  • Kotlin

@Configuration
class ExternalizationConfiguration {

  @Bean
  EventExternalizationConfiguration eventExternalizationConfiguration() {

    return EventExternalizationConfiguration.externalizing()                 (1)
      .select(EventExternalizationConfiguration.annotatedAsExternalized())   (2)
      .mapping(SomeEvent.class, event -> …)                                  (3)
      .headers(event -> …)                                                   (4)
      .routeKey(WithKeyProperty.class, WithKeyProperty::getKey)              (5)
      .build();
  }
}
@Configuration
class ExternalizationConfiguration {

  @Bean
  fun eventExternalizationConfiguration(): EventExternalizationConfiguration {

    EventExternalizationConfiguration.externalizing()                         (1)
      .select(EventExternalizationConfiguration.annotatedAsExternalized())    (2)
      .mapping(SomeEvent::class.java) { event -> … }                          (3)
      .headers() { event -> … }                                               (4)
      .routeKey(WithKeyProperty::class.java, WithKeyProperty::getKey)         (5)
      .build()
  }
}
1 まず、EventExternalizationConfiguration のデフォルトのインスタンスを作成します。
2 前の呼び出しで返された Selector インスタンスの select(…) メソッドの 1 つを呼び出して、イベントの選択をカスタマイズします。このステップでは、アノテーションのみを検索するため、アプリケーションベースパッケージフィルターが基本的に無効になります。型、パッケージ、パッケージ、アノテーションによってイベントを簡単に選択するための便利なメソッドが存在します。また、選択とルーティングを 1 つのステップで定義するショートカット。
3SomeEvent インスタンスのマッピングステップを定義します。ルーターで  … .routeMapped() を追加で呼び出さない限り、ルーティングは元のイベントインスタンスによって決定されることに注意してください。
4 送信されるメッセージに、示されているように一般的なヘッダー、または特定のペイロード型に固有のヘッダーを追加します。
5 最後に、イベントインスタンスの値を抽出するメソッドハンドルを定義して、ルーティングキーを決定します。あるいは、前の呼び出しから返された Router インスタンスに対して一般的な route(…) メソッドを使用して、個々のイベントに対して完全な RoutingKey を生成することもできます。

イベントの外部化の直列化

Spring Modulith のイベント外部化は、トランザクションイベントリスナーとして実装されています。つまり、複数のスレッドが同時にブローカーとのやり取りをトリガーする可能性があります。これは、イベントの発行が再送信される際に特に重要になります。ブローカーはやり取りの急増を検知する可能性があるため、一部のやり取りに少し時間がかかることがあり、後続のイベントの外部化が以前のイベントを追い越してしまう可能性があります。

これを防ぐには、spring.modulith.events.externalization.serialize-externalization プロパティを true に設定して、ブローカーとのやり取りを直列化し、一度に 1 つのイベントのみが送信されるようにすることができます。

Namastack アウトボックスのサポート

高度なアウトボックス機能が必要な場合は、イベントの外部化をナマスタックアウトボックス (英語) に委譲できます。この機能は現在、リレーショナルデータベースでのみ利用可能です。この機能を有効にするには、まず Spring Modulith Namastack スターターをプロジェクトに追加してください。

Spring Modulith Namastack スターターを宣言します
  • Maven

  • Gradle

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-starter-namastack</artifactId>
</dependency>
dependencies {
  implementation 'org.springframework.modulith:spring-modulith-starter-namastack'
}

イベントの外部化を Namastack に自動的に委譲するには、spring.modulith.events.externalization.mode プロパティを outbox に切り替えてください。Namastack アウトボックスの動作をカスタマイズする方法の詳細については、Namastack のリファレンスドキュメント (英語) を参照してください。Github リポジトリにサンプル [GitHub] (英語) があります。

JobRunr アウトボックスのサポート

イベントを外部化する Namastack のサポートと同様に、JobRunr (英語) もサポートしています。この機能を有効にするには、まず Spring Modulith JobRunr スターターをプロジェクトに追加してください。

Spring Modulith JobRunr スターターを宣言します
  • Maven

  • Gradle

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-starter-jobrunr</artifactId>
</dependency>
dependencies {
  implementation 'org.springframework.modulith:spring-modulith-starter-jobrunr'
}

イベント外部化を自動的に JobRunr に委譲するには、spring.modulith.events.externalization.mode プロパティを outbox に切り替えてください。JobRunr の詳細については、リファレンスドキュメント (英語) を参照してください。Github リポジトリに例 [GitHub] (英語) があります。

公開されたイベントのテスト

次のセクションでは、Spring アプリケーションイベントの追跡のみに焦点を当てたテストアプローチについて説明します。@ApplicationModuleListener を使用するモジュールのテストに関するより包括的なアプローチについては、Scenario API を確認してください。

Spring Modulith の @ApplicationModuleTest を使用すると、テストメソッドに PublishedEvents インスタンスを挿入して、テスト対象のビジネス操作の過程で特定のイベントセットが公開されたかどうかを確認できるようになります。

アプリケーションモジュール配置のイベントベース統合テスト
  • Java

  • Kotlin

@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  void someTestMethod(PublishedEvents events) {

    // …
    var matchingMapped = events.ofType(OrderCompleted.class)
      .matching(OrderCompleted::getOrderId, reference.getId());

    assertThat(matchingMapped).hasSize(1);
  }
}
@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  fun someTestMethod(events: PublishedEvents events) {

    // …
    val matchingMapped = events.ofType(OrderCompleted::class.java)
      .matching(OrderCompleted::getOrderId, reference.getId())

    assertThat(matchingMapped).hasSize(1)
  }
}

PublishedEvents が特定の条件に一致するイベントを選択するための API を公開するメソッドに注目してください。検証は、予想される要素の数を検証する AssertJ アサーションによって完了します。いずれにしても、これらのアサーションに AssertJ を使用している場合は、テストメソッドパラメーター型として AssertablePublishedEvents を使用し、それを通じて提供される Fluent アサーション API を使用することもできます。

AssertablePublishedEvents を使用したイベントパブリケーションの検証
  • Java

  • Kotlin

@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  void someTestMethod(AssertablePublishedEvents events) {

    // …
    assertThat(events)
      .contains(OrderCompleted.class)
      .matching(OrderCompleted::getOrderId, reference.getId());
  }
}
@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  fun someTestMethod(events: AssertablePublishedEvents) {

    // …
    assertThat(events)
      .contains(OrderCompleted::class.java)
      .matching(OrderCompleted::getOrderId, reference.getId())
  }
}

assertThat(…) 式によって返される型によって、公開されたイベントに制約を直接定義できることに注意してください。