[[events]]
:imagesdir: images
[[working-with-application-events]]
= Working with Application Events

To keep application modules as decoupled as possible from each other, their primary means of interaction should be event publication and consumption.
This avoids the originating module to know about all potentially interested parties, which is a key aspect to enable application module integration testing (see xref:testing.adoc[Integration Testing Application Modules]).

Often we will find application components defined like this:

[tabs]
======
Java::
+
[source, java, role="primary"]
----
@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);
  }
}
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Service
class OrderManagement(val inventory: InventoryManagement) {

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

The `complete(…)` method creates functional gravity in the sense that it attracts related functionality and thus interaction with Spring beans defined in other application modules.
This especially makes the component harder to test as we need to have instances available of those depended on beans just to create an instance of `OrderManagement` (see xref:testing.adoc#efferent-dependencies[Dealing with Efferent Dependencies]).
It also means that we will have to touch the class whenever we would like to integrate further functionality with the business event order completion.

We can change the application module interaction as follows:

.Publishing an application event via Spring's `ApplicationEventPublisher`
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@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()));
  }
}
----

Kotlin::
+
[source, kotlin, role="secondary"]
----
@Service
class OrderManagement(val events: ApplicationEventPublisher, val dependency: OrderInternal) {

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

Note how, instead of depending on the other application module's Spring bean, we use Spring's `ApplicationEventPublisher` to publish a domain event once we have completed the state transitions on the primary aggregate.
For a more aggregate-driven approach to event publication, see https://docs.spring.io/spring-data/commons/reference/repositories/core-domain-events.html[Spring Data's application event publication mechanism] for details.
As event publication happens synchronously by default, the transactional semantics of the overall arrangement stay the same as in the example above.
Both for the good, as we get to a very simple consistency model (either both the status change of the order _and_ the inventory update succeed or none of them does), but also for the bad as more triggered related functionality will widen the transaction boundary and potentially cause the entire transaction to fail, even if the functionality that is causing the error is not crucial.

A different way of approaching this is by moving the event consumption to asynchronous handling at transaction commit and treat secondary functionality exactly as that:

.An async, transactional event listener
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@Component
class InventoryManagement {

  @Async
  @TransactionalEventListener
  void on(OrderCompleted event) { /* … */ }
}
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Component
class InventoryManagement {

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

This now effectively decouples the original transaction from the execution of the listener.
While this avoids the expansion of the original business transaction, it also creates a risk: if the listener fails for whatever reason, the event publication is lost, unless each listener actually implements its own safety net.
Even worse, that doesn't even fully work, as the system might fail before the method is even invoked.

[[aml]]
== Application Module Listener

To run a transactional event listener in a transaction itself, it would need to be annotated with `@Transactional` in turn.

.An async, transactional event listener running in a transaction itself
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@Component
class InventoryManagement {

  @Async
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  @TransactionalEventListener
  void on(OrderCompleted event) { /* … */ }
}
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Component
class InventoryManagement {

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

To ease the declaration of what is supposed to describe the default way of integrating modules via events, Spring Modulith provides `@ApplicationModuleListener` as a shortcut.

.An application module listener
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@Component
class InventoryManagement {

  @ApplicationModuleListener
  void on(OrderCompleted event) { /* … */ }
}
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Component
class InventoryManagement {

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

[[publication-registry]]
== The Event Publication Registry

Spring Modulith ships with an event publication registry that hooks into the core event publication mechanism of Spring Framework.
On event publication, it finds out about the transactional event listeners that will get the event delivered and writes entries for each of them (dark blue) into an event publication log as part of the original business transaction.

.The transactional event listener arrangement before execution
image::event-publication-registry-start.png[]

Each transactional event listener is wrapped into an aspect that marks that log entry as completed if the execution of the listener succeeds.
In case the listener fails, the log entry stays untouched so that retry mechanisms can be deployed depending on the application's needs.
Automatic re-publication of the events can be enabled via the xref:appendix.adoc#configuration-properties[`spring.modulith.events.republish-outstanding-events-on-restart`] property.

.The transactional event listener arrangement after execution
image::event-publication-registry-end.png[]

[[publication-registry.starters]]
=== Spring Boot Event Registry Starters

Using the transactional event publication log requires a combination of artifacts added to your application.
To ease that task, Spring Modulith provides starter POMs that are centered around the <<publication-registry.publication-repositories, persistence technology>> to be used and default to the Jackson-based <<publication-registry.serialization, EventSerializer>> implementation.
The following starters are available:

[%header,cols="1,3,6"]
|===
|Persistence Technology|Artifact|Description

|JPA
|`spring-modulith-starter-jpa`
|Using JPA as persistence technology.

|JDBC
|`spring-modulith-starter-jdbc`
|Using JDBC as persistence technology. Also works in JPA-based applications but bypasses your JPA provider for actual event persistence.

|MongoDB
|`spring-modulith-starter-mongodb`
|Using MongoDB as persistence technology. Also enables MongoDB transactions and requires a replica set setup of the server to interact with. The transaction auto-configuration can be disabled by setting the `spring.modulith.events.mongobd.transaction-management.enabled` property to `false`.

|Neo4j
|`spring-modulith-starter-neo4j`
|Using Neo4j behind Spring Data Neo4j.
|===

[[publication-registry.managing-publications]]
=== Managing Event Publications

Event publications may need to be managed in a variety of ways during the runtime of an application.
Incomplete publications might have to be re-submitted to the corresponding listeners after a given amount of time.
Completed publications on the other hand, will likely have to be purged from the database or moved into an archive store.
As the needs for that kind of housekeeping strongly vary from application to application, Spring Modulith offers an API to deal with both kinds of publications.
That API is available through the `spring-modulith-events-api` artifact that you can add to your application:

.Using Spring Modulith Events API artifact
[tabs]
======
Maven::
+
[source, xml, subs="+attributes", role="primary"]
----
<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-events-api</artifactId>
  <version>{projectVersion}</version>
</dependency>
----

Gradle::
+
[source, subs="+attributes", role="secondary"]
----
dependencies {
  implementation 'org.springframework.modulith:spring-modulith-events-api:{projectVersion}'
}
----
======

This artifact contains two primary abstractions that are available to application code as Spring Beans:

* `CompletedEventPublications` -- This interface allows accessing all completed event publications, and provides an API to immediately purge all of them from the database or the completed publications older than a given duration (for example, 1 minute).
* `IncompleteEventPublications` -- This interface allows accessing all incomplete event publications to resubmit either the ones matching a given predicate or older than a given `Duration` relative to the original publishing date.

[[publication-registry.completion]]
=== Event Publication Completion

Event publications are marked as completed when a transactional or `@ApplicationModuleListener` execution completes successfully.
By default, the completion is registered by setting the completion date on an `EventPublication`.
This means that completed publications will remain in the Event Publication Registry so that they can be inspected through the `CompletedEventPublications` interface as described xref:events.adoc#publication-registry.managing-publications[above].
A consequence of this is that you'll need to put some code in place that will periodically purge old, completed ``EventPublication``s.
Otherwise, the persistent abstraction of them, for example a relational database table, will grow unbounded and the interaction with the store creating and completing new ``EventPublication`` might slow down.

Spring Modulith 1.3 introduces a configuration property `spring.modulith.events.completion-mode`.
It defaults to `UPDATE` which is backed by the strategy described above.
Alternatively, the completion mode can be set to `DELETE`, which alters the registry's persistence mechanisms to rather delete ``EventPublication``s on completion.
This means that `CompletedEventPublications` will not return any publications anymore, but at the same time, you don't have to worry about purging the completed events from the persistence store manually anymore.

[[publication-registry.publication-repositories]]
=== Event Publication Repositories

To actually write the event publication log, Spring Modulith exposes an `EventPublicationRepository` SPI and implementations for popular persistence technologies that support transactions, like JPA, JDBC and MongoDB.
You select the persistence technology to be used by adding the corresponding JAR to your Spring Modulith application.
We have prepared dedicated xref:events.adoc#starters[starters] to ease that task.

The JDBC-based implementation can create a dedicated table for the event publication log when the respective configuration property (`spring.modulith.events.jdbc.schema-initialization.enabled`) is set to `true`.
For details, please consult the xref:appendix.adoc#schemas[schema overview] in the appendix.

[[publication-registry.serialization]]
=== Event Serializer

Each log entry contains the original event in serialized form.
The `EventSerializer` abstraction contained in `spring-modulith-events-core` allows plugging different strategies for how to turn the event instances into a format suitable for the datastore.
Spring Modulith provides a Jackson-based JSON implementation through the `spring-modulith-events-jackson` artifact, which registers a `JacksonEventSerializer` consuming an `ObjectMapper` through standard Spring Boot auto-configuration by default.

[[publication-registry.customize-publication-date]]
=== Customizing the Event Publication Date

By default, the Event Publication Registry will use the date returned by the `Clock.systemUTC()` as event publication date.
If you want to customize this, register a bean of type clock with the application context:

[source, java]
----
@Configuration
class MyConfiguration {

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

[[externalization]]
== Externalizing Events

Some of the events exchanged between application modules might be interesting to external systems.
Spring Modulith allows publishing selected events to a variety of message brokers.
To use that support you need to take the following steps:

1. Add the <<externalization.infrastructure, broker-specific Spring Modulith artifact>> to your project.
2. Select event types to be externalized by annotating them with either Spring Modulith's or jMolecules' `@Externalized` annotation.
3. Specify the broker-specific routing target in the annotation's value.

To find out how to use other ways of selecting events for externalization, or customize their routing within the broker, check out <<externalization.fundamentals>>.

[[externalization.infrastructure]]
=== Supported Infrastructure

[%header,cols="1,3,6"]
|===
|Broker|Artifact|Description

|Kafka
|`spring-modulith-events-kafka`
|Uses Spring Kafka for the interaction with the broker.
The logical routing key will be used as Kafka's topic and message key.

|AMQP
|`spring-modulith-events-amqp`
|Uses Spring AMQP for the interaction with any compatible broker.
Requires an explicit dependency declaration for Spring Rabbit for example.
The logical routing key will be used as AMQP routing key.

|JMS
|`spring-modulith-events-jms`
|Uses Spring's core JMS support.
Does not support routing keys.

|SQS
|`spring-modulith-events-aws-sqs`
|Uses Spring Cloud AWS SQS support.
The logical routing key will be used as SQS message group id.
When routing key is set, requires SQS queue to be configured as a FIFO queue.

|SNS
|`spring-modulith-events-aws-sns`
|Uses Spring Cloud AWS SNS support.
The logical routing key will be used as SNS message group id.
When routing key is set, requires SNS to be configured as a FIFO topic with content based deduplication enabled.

|Spring Messaging
|`spring-modulith-events-messaging`
|Uses Spring's core `Message` and `MessageChannel` support.
Resolves the target `MessageChannel` by its bean name given the `target` in the `Externalized` annotation.
Forwards routing information as a header - called `springModulith_routingTarget` - to be processed in whatever way by downstream components, typically in a Spring Integration `IntegrationFlow`.

|===

[[externalization.fundamentals]]
=== Fundamentals of Event Externalization

The event externalization performs three steps on each application event published.

1. _Determining whether the event is supposed to be externalized_ -- We refer to this as "`event selection`".
By default, only event types located within a Spring Boot auto-configuration package and annotated with one of the supported `@Externalized` annotations are selected for externalization.
2. _Mapping the event (optional)_ -- By default, the event is serialized to JSON using the Jackson `ObjectMapper` present in the application and published as is.
The mapping step allows developers to either customize the representation or even completely replace the original event with a representation suitable for external parties.
Note that the mapping step precedes the actual serialization of the to be published object.
3. _Determining a routing target_ -- Message broker clients need a logical target to publish the message to.
The target usually identifies physical infrastructure (a topic, exchange, or queue depending on the broker) and is often statically derived from the event type.
Unless defined in the `@Externalized` annotation specifically, Spring Modulith uses the application-local type name as target.
In other words, in a Spring Boot application with a base package of `com.acme.app`, an event type `com.acme.app.sample.SampleEvent` would get published to `sample.SampleEvent`.
+
Some brokers also allow to define a rather dynamic routing key, that is used for different purposes within the actual target.
By default, no routing key is used.

[[externalization.annotations]]
=== Annotation-based Event Externalization Configuration

To define a custom routing key via the `@Externalized` annotations, a pattern of `$target::$key` can be used for the target/value attribute available in each of the particular annotations.
Both the target and key can be a SpEL expression which will get the event instance configured as root object.

.Defining a dynamic routing key via SpEL expression
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@Externalized("customer-created::#{#this.getLastname()}") // <2>
class CustomerCreated {

  String getLastname() { // <1>
    // …
  }
}
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Externalized("customer-created::#{#this.getLastname()}") // <2>
class CustomerCreated {
  fun getLastname(): String { // <1>
    // …
  }
}
----
======

The `CustomerCreated` event exposes the last name of the customer via an accessor method.
That method is then used via the ``&#35;this.getLastname()`` expression in key expression following the `::` delimiter of the target declaration.

If the key calculation becomes more involved, it is advisable to rather delegate that into a Spring bean that takes the event as argument:

.Invoking a Spring bean to calculate a routing key
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@Externalized("…::#{@beanName.someMethod(#this)}")
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Externalized("…::#{@beanName.someMethod(#this)}")
----
======

[[externalization.api]]
=== Programmatic Event Externalization Configuration

The `spring-modulith-events-api` artifact contains `EventExternalizationConfiguration` that allows developers to customize all of the above mentioned steps.

.Programmatically configuring event externalization
[tabs]
======
Java::
+
[source, java, role="primary"]
----
@Configuration
class ExternalizationConfiguration {

  @Bean
  EventExternalizationConfiguration eventExternalizationConfiguration() {

    return EventExternalizationConfiguration.externalizing()                 // <1>
      .select(EventExternalizationConfiguration.annotatedAsExternalized())   // <2>
      .mapping(SomeEvent.class, event -> …)                                  // <3>
      .routeKey(WithKeyProperty.class, WithKeyProperty::getKey)              // <4>
      .build();
  }
}
----
Kotlin::
+
[source, kotlin, role="secondary"]
----
@Configuration
class ExternalizationConfiguration {

  @Bean
  fun eventExternalizationConfiguration(): EventExternalizationConfiguration {

    EventExternalizationConfiguration.externalizing()                         // <1>
      .select(EventExternalizationConfiguration.annotatedAsExternalized())    // <2>
      .mapping(SomeEvent::class.java) { event -> … }                           // <3>
      .routeKey(WithKeyProperty::class.java, WithKeyProperty::getKey)         // <4>
      .build()
  }
}
----
======

<1> We start by creating a default instance of `EventExternalizationConfiguration`.
<2> We customize the event selection by calling one of the `select(…)` methods on the `Selector` instance returned by the previous call.
This step fundamentally disables the application base package filter as we only look for the annotation now.
Convenience methods to easily select events by type, by packages, packages and annotation exist.
Also, a shortcut to define selection and routing in one step.
<3> We define a mapping step for `SomeEvent` instances.
Note that the routing will still be determined by the original event instance, unless you additionally call `….routeMapped()` on the router.
<4> We finally determine a routing key by defining a method handle to extract a value of the event instance.
Alternatively, a full `RoutingKey` can be produced for individual events by using the general `route(…)` method on the `Router` instance returned from the previous call.

[[testing]]
== Testing published events

NOTE: The following section describes a testing approach solely focused on tracking Spring application events.
For a more holistic approach on testing modules that use xref:testing.adoc[`@ApplicationModuleListener`], please check out the xref:testing.adoc#scenarios[`Scenario` API].

Spring Modulith's `@ApplicationModuleTest` enables the ability to get a `PublishedEvents` instance injected into the test method to verify a particular set of events has been published during the course of the business operation under test.

.Event-based integration testing of the application module arrangement
[tabs]
======
Java::
+
[source, java, subs="quotes", role="primary"]
----
@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  void someTestMethod(**PublishedEvents events**) {

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

    assertThat(matchingMapped).hasSize(1);
  }
}
----
Kotlin::
+
[source, kotlin, subs="quotes", role="secondary"]
----
@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)
  }
}
----
======

Note how `PublishedEvents` exposes an API to select events matching a certain criteria.
The verification is concluded by an AssertJ assertion that verifies the number of elements expected.
If you are using AssertJ for those assertions anyway, you can also use `AssertablePublishedEvents` as test method parameter type and use the fluent assertion APIs provided through that.

.Using `AssertablePublishedEvents` to verify event publications
[tabs]
======
Java::
+
[source, java, subs="quotes", role="primary"]
----
@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  void someTestMethod(**AssertablePublishedEvents events**) {

    // …
    assertThat(events)
      .contains(OrderCompleted.class)
      .matching(OrderCompleted::getOrderId, reference.getId());
  }
}
----
Kotlin::
+
[source, kotlin, subs="quotes", role="secondary"]
----
@ApplicationModuleTest
class OrderIntegrationTests {

  @Test
  fun someTestMethod(events: AssertablePublishedEvents) {

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

Note how the type returned by the `assertThat(…)` expression allows to define constraints on the published events directly.
