© 2022 The original authors.

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

Preface

Project Metadata

Using Spring Modulith

Spring Modulith consists of a set of libraries that can be used individually and depending on which features of it you would like to use. To ease the declaration of the individual modules, we recommend to declare the following BOM in your Maven POM:

Using the Spring Modulith BOM
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.experimental</groupId>
      <artifactId>spring-modulith-bom</artifactId>
      <version>0.1.0</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

The individual sections describing Spring Modulith features will refer to the individual artifacts that are needed to make use of the feature. For an overview about all modules available, have a look at Spring Modulith modules.

1. Fundamentals

Spring Modulith supports developers implementing logical modules in Spring Boot applications. It allows them to apply structural validation, document the module arrangement, run integration tests for individual modules, observe the modules interaction at runtime and generally implement module interaction in a loosely-coupled way. This section will discuss the fundamental concepts that developers need to understand before diving into the technical support.

1.1. Application modules

In a Spring Boot application, an application module is a unit of functionality that consists of the following parts:

  • An API exposed to other modules implemented by Spring bean instances and application events published by the module, usually referred to as provided interface.

  • Internal implementation components that are not supposed to be accessed by other modules.

  • References to API exposed by other modules in the form of Spring bean dependencies, application events listened to and configuration properties exposed, usually referred to as required interface.

Spring Moduliths provides different ways of expressing modules within Spring Boot applications, primarily differing in the level of complexity involved in the overall arrangement. This allows developers to start simple and naturally move to more sophisticated means as and if needed.

1.1.1. Simple Application Modules

The application’s main package is the one that the main application class resides in. That is the class, that is annotated with @SpringBootApplication and usually contains the main(…) method used to run it. By default, each direct sub-package of the main package is considered an application module package.

If this package does not contain any sub-packages, it is considered a simple one. It allows to hide code inside it by using Java’s package scope to hide types from being referred to by code residing in other packages and thus not subject for dependency injection into those. Thus, naturally, the module’s API consists of all public types in the package.

Let us have a look at an example arrangement ( denotes a public type, a package protected one).

A single inventory application module
 Example
└─  src/main/java
   ├─  example                        (1)
   |  └─  Application.java
   └─  example.inventory              (2)
      ├─  InventoryManagement.java
      └─  SomethingInventoryInternal.java
1 The application’s main package example.
2 An application module package inventory.

1.1.2. Advanced Application Modules

If an application module package contains sub-packages, types in those might need to be made public so that it can be referred to from code of the very same module.

An inventory and order application module
 Example
└─  src/main/java
   ├─  example
   |  └─  Application.java
   ├─  example.inventory
   |  ├─  InventoryManagement.java
   |  └─  SomethingInventoryInternal.java
   ├─  example.order
   |  └─  OrderManagement.java
   └─  example.order.internal
      └─  SomethingOrderInternal.java

In such an arrangement, the order package is considered an API package. Code from other application modules is allowed to refer to types within that. order.internal, just as any other sub-package of the application module base package are considered internal ones. Code within those must not be referred to from other modules. Note, how SomethingOrderInternal is a public type, likely because OrderManagement depends on it. This unfortunately means, that it can also be referred to from other packages such as the inventory one. In this case, the Java compiler is not of much use to prevent these illegal references.

1.1.3. Explicit Application Module Dependencies

A module can opt into declaring its allowed dependencies by using the @ApplicationModule annotation on the package-info.java type.

Inventory explicitly configuring module dependencies
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order"
)
package example.inventory;

In this case code within the inventory module was only allowed to refer to code in the order module (and code not assigned to any module in the first place). Find out about how to monitor that in Verifying Application Module Structure.

1.1.4. The ApplicationModules Type

Spring Moduliths allows to inspect a codebase to derive an application module model based on the given arrangement and optional configuration. The spring-modulith-core artifact contains ApplicationModules that can be pointed to a Spring Boot application class:

Creating an application module model
var modules = ApplicationModules.of(Application.class);

To get an impression about what the analyzed arrangement looks like, we can just write the individual modules contained in the overall model to the console:

Writing the application module arranagement to the console
modules.forEach(System.out::println);
The console output of our application module arrangement
## example.inventory ##
> Logical name: inventory
> Base package: example.inventory
> Spring beans:
  + ….InventoryManagement
  o ….SomeInternalComponent

## example.order ##
> Logical name: order
> Base package: example.order
> Spring beans:
  + ….OrderManagement
  + ….internal.SomeInternalComponent

Note, how each module is listed and the contained Spring components are identified and the respective visibility is rendered, too.

1.1.5. Named Interfaces

By default and as described in Advanced Application Modules, an application module’s base package is considered the API package and thus is the only package to allow incoming dependencies from other modules. In case you would like to expose additional packages to other modules, you need to use named interfaces. You achieve that by annotating the package-info.java file of those package with @NamedInterface.

A package arrangement to encapsulate an SPI named interface
 Example
└─  src/main/java
   ├─  example
   |  └─  Application.java
   ├─ …
   ├─  example.order
   |  └─  OrderManagement.java
   ├─  example.order.spi
   |  ├—  package-info.java
   |  └─  SomeSpiInterface.java
   └─  example.order.internal
      └─  SomethingOrderInternal.java
package-info.java in example.order.spi
@org.springframework.modulith.NamedInterface("spi")
package example.order.spi;

The effect of that declaration is two fold: first, code in other application modules is allowed to refer to SomeSpiInterface. Application modules are able to refer to the named interface in explicit dependency declarations. Assume the inventory module was making use of that, it could refer to the above declared named interface like this:

@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order::spi"
)
package example.inventory;

Note how we concatenate the named interface’s name spi via the double colon ::. In this setup, code in inventory would be allowed to depend on SomeSpiInterface and other code residing in the order.spi interface, but not on OrderManagement for example. For modules without explicitly described dependencies, both the application module root package and the SPI one are accessible.

1.1.6. Customizing Module Detection

If the default application module model does not work for your application, the detection of the modules can be customized by providing an implementation of ApplicationModuleDetectionStrategy. That interface exposes a single method Stream<JavaPackage> getModuleBasePackages(JavaPackage) and will be called with the package, the Spring Boot application class resides in. You can then inspect the packages residing within that and select the ones to be considered application module base packages based on a naming convention or the like.

Assume you declare a custom ApplicationModuleDetectionStrategy implementation like this:

package example;

class CustomApplicationModuleDetectionStrategy implements ApplicationModuleDetectionStrategy {

  @Override
  public Stream<JavaPackage> getModuleBasePackages(JavaPackage basePackage) {
    // Your module detection goes here
  }
}

This class needs to be registered in META-INF/spring.factories as follows:

org.springframework.modulith.model.ApplicationModuleDetectionStrategy=\
  example.CustomApplicationModuleDetectionStrategy

2. Verifying Application Module Structure

We can verify whether our code arrangement adheres to the intended constraints by calling the ….verify() method on our ApplicationModules instance:

ApplicationModules.of(Application.class).verify();

The verification includes the following rules:

  • No cycles on the application module level — the dependencies between modules have to form directed, acyclic graph.

  • Efferent module access via API packages only — All references to types that reside in application module internal packages are rejected. See Advanced Application Modules for details.

  • Explicitly allowed application module dependencies only (optional) — An application module can optionally define allowed dependencies via @ApplicationModule(allowedDependencies = …). If those are configured, dependencies to other application modules are rejected. See Explicit Application Module Dependencies and Named Interfaces for details.

Spring Modulith optionally integrates with the jMolecules ArchUnit library and, if present, automatically triggers its verification rules described here.

3. Integration Testing Application Modules

Spring Modulith allows to run integration tests bootstrapping individual application modules in isolation or combination with others. To achieve this, place JUnit test class in an application module package or any sub-package of that and annotate it with @ApplicationModuleTest:

A application module integration test class
package example.order;

@ApplicationModuleTest
class OrderIntegrationTests {

  // Individual test cases go here
}

This will run your integration test similar to what @SpringBootTest would have achieved but with the bootstrap actually limited to the application module the test resides in. If you configure the log level for org.springframework.modulith to DEBUG, you will see detailed information about how the test execution customizes the Spring Boot bootstrap:

The log output of a application module integration test bootstrap
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v3.0.0-SNAPSHOT)

… - Bootstrapping @ApplicationModuleTest for example.order in mode STANDALONE (class example.Application)…
… - ======================================================================================================
… - ## example.order ##
… - > Logical name: order
… - > Base package: example.order
… - > Direct module dependencies: none
… - > Spring beans:
… -       + ….OrderManagement
… -       + ….internal.OrderInternal
… - Starting OrderIntegrationTests using Java 17.0.3 …
… - No active profile set, falling back to 1 default profile: "default"
… - Re-configuring auto-configuration and entity scan packages to: example.order.

Note, how the output contains the detailed information about the module included in the test run. It creates the application module module, finds the module to be run and limits the application of auto-configuration, component and entity scanning to the corresponding packages.

3.1. Bootstrap Modes

The application module test can be bootstrapped in a variety of modes:

  • STANDALONE (default) — Runs the current module only.

  • DIRECT_DEPENDENCIES — Runs the current module as well as all modules the current one directly depends on.

  • ALL_DEPENDENCIES — Runs the current module and the entire tree of modules depended on.

3.2. Dealing with Efferent Dependencies

When an application module is bootstrapped, the Spring beans it contains will be instantiated. If those contain bean references that cross module boundaries, the bootstrap will fail if those other modules are not included in the test run (see Bootstrap Modes for details). While a natural reaction might be to expand the scope of the application modules included, it is usually a better option to mock the target beans.

Mocking Spring bean dependencies in other application modules
@ApplicationModuleTest
class InventoryIntegrationTests {

  @MockBean SomeOtherComponent someOtherComponent;
}

Spring Boot will create bean definitions and instances for the types defined as @MockBean and add them to the ApplicationContext bootstrapped for the test run.

If you find your application module depending on too many beans of other ones, that is usually a sign of high coupling between them. The dependencies should be reviewed for whether they are candidates for replacement by publishing a domain event (see Working with Application Events).

4. 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 Integration Testing Application Modules).

Often we will find application components defined like this:

@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);
  }
}

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 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
@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()));
  }
}

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 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
@Service
@RequiredArgsConstructor
public class InventoryManagement {

  @Async
  @TransactionalEventListener
  void on(OrderCompleted event) {

  }
}

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.

4.1. 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.

event publication registry start
Figure 1. The transactional event listener arrangement before execution

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. By default, all incomplete event publications are resubmitted at application startup.

event publication registry end
Figure 2. The transactional event listener arrangement after execution

4.2. 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 starters to ease that task.

The JDBC-based implementation will create a dedicated table for the event publication log, unless a table with a particular name already exists. For details, please consult the schema overview in the appendix.

4.3. 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.

4.4. 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 persistence technology to be used and default to the Jackson-based EventSerializer implementation. The following starters are available:

  • spring-modulith-starter-jpa — Using JPA as persistence technology.

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

  • spring-modulith-starter-mongodb — Using MongoDB behind Spring Data MongoDB.

4.5. Integration Testing Application Modules Working with Events

Integration tests for application modules that interact with other modules' Spring beans usually have those mocked and the test cases verify the interaction by verifying that that mock bean was invoked in a particular way.

Traditional integration testing of the application module interaction
@ApplicationModuleTest
class InventoryIntegrationTests {

  @MockBean SomeOtherComponent someOtherComponent;

  @Test
  void someTestMethod() {

    // Given
    // When
    // Then
    verify(someOtherComponent).someMethodCall();
  }
}

In an event-based application interaction model, the dependency to the other application module’s Spring bean is gone and we have nothing to verify. 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 intergration testing of the application module arrangement
@ApplicationModuleTest
class InventoryIntegrationTests {

  @Test
  void someTestMethod(PublishedEvents events) {

    // Given
    // When
    // Then
    assertThat(events.…).…;
  }
}

5. Moments — a Passage of Time Events API

modulith-moments is a Passage of Time Events implementation heavily inspired by Matthias Verraes blog post. It’s an event-based approach to time to trigger actions that are tied to a particular period of time having passed.

To use the abstraction, include the following dependency in your project:

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>modulith-moments</artifactId>
</dependency>

The dependency added to the project’s classpath causes the following things in your application:

  • Application code can refer to HourHasPassed, DayHasPassed, WeekHasPassed, MonthHasPassed, QuarterHasPassed, YearHasPassed types in Spring event listeners to get notified if a certain amount of time has passed.

  • A bean of type org.springframework.modulith.Moments is available in the ApplicationContext that contains the logic to trigger these events.

  • If spring.modulith.moments.enable-time-machine is set to true, that instance will be a org.springframework.modulith.TimeMachine which allows to "shift" time and by that triggers all intermediate events, which is useful to integration test functionality that is triggered by the events.

By default, Moments uses a Clock.systemUTC() instance. To customize this, declare a bean of type Clock.

@Configuration
class MyConfiguration {

  @Bean
  Clock myCustomClock() {
    // Create a custom Clock here
  }
}

Moments exposes the following application properties for advanced customization:

Table 1. Available application properties
Property Default value Description

spring.modulith.moments.enable-time-machine

false

If set to true, the Moments instance will be a TimeMachine, that exposes API to shift time forward. Useful for integration tests that expect functionality triggered by the Passage of Time Events.

spring.modulith.moments.granularity

hours

The minimum granularity of events to be fired. Alternative value days to avoid hourly events.

spring.modulith.moments.locale

Locale.getDefault()

The Locale to use when determining week boundaries.

spring.modulith.moments.quarter-start-month

Months.JANUARY

The month at which quarters start.

spring.modulith.moments.zone-id

ZoneOffset#UTC

The ZoneId to determine times which are attached to the events published.

6. Documenting Application Modules

The application module model created via ApplicationModules can be used to create documentation snippets for inclusion into developer documentation written in Asciidoc. Spring Modulith’s Documenter abstraction can produce two different kinds of snippets:

  • C4 and UML component diagrams describing the relationships between the individual application modules

  • A so-called Application Module Canvas, a tabular overview about the module and the most relevant elements in those (Spring beans, aggregate roots, events published and listened to as well as configuration properties).

6.1. Generating Application Module Component diagrams

The documentation snippets can be generated by handing the ApplicationModules instance into a Documenter.

Generating application module component diagrams using Documenter
class DocumentationTests {

  ApplicationModules modules = ApplicationModules.of(Application.class);

  @Test
  void writeDocumentationSnippets() {

    new Documenter(modules)
      .writeModulesAsPlantUml()
      .writeIndividualModulesAsPlantUml();
  }
}

The first call on Documenter will generate a C4 component diagram containing all modules within the system.

c4 all modules
Figure 3. All modules and their relationships rendered as C4 component diagram

The second call will create additional diagrams that only include the individual module and the ones they directly depend on on the canvas.

c4 individual modules
Figure 4. A subset of application modules and their relationships starting from the order module rendered as C4 component diagram

6.1.1. Using Traditional UML Component Diagrams

If you prefer the traditional UML style component diagrams, tweak the DiagramOptions to rather use that style as follows:

DiagramOptions.defaults()
  .withStyle(DiagramStyle.UML);

This will cause the diagrams to look like this:

uml all modules
Figure 5. All modules and their relationships rendered as UML component diagram
uml individiual module
Figure 6. A subset of application modules and their relationships starting from the order module rendered as UML component diagram

6.2. Generating Application Module Canvases

The Application Module Canvases can be generated by calling Documenter.writeModuleCanvases():

Generating application module component diagrams using Documenter
class DocumentationTests {

  ApplicationModules modules = ApplicationModules.of(Application.class);

  @Test
  void writeDocumentationSnippets() {

    new Documenter(modules)
      .writeModuleCanvases();
  }
}

A canvas generated looks like this:

Table 2. A sample Application Module Canvas

Base package

com.acme.commerce.inventory

Spring components

Services

  • c.a.c.i.InventoryManagement

Repositories

  • c.a.c.i.Inventory

Event listeners

  • c.a.c.i.InternalInventoryListeners listening to o.s.m.m.DayHasPassed, c.a.c.i.QuantityReduced

  • c.a.c.i.InventoryOrderEventListener listening to c.a.c.o.OrderCanceled, c.a.c.o.OrderCompleted

Configuration properties

  • c.a.c.i.InventoryProperties

Others

  • c.a.c.i.InventoryItemCreationListener

Aggregate roots

  • c.a.c.i.InventoryItem

Published events

  • c.a.c.i.QuantityReduced created by:

    • c.a.c.i.InventoryItem.decreaseQuantity(…)

  • c.a.c.i.StockShort created by:

    • c.a.c.i.InternalInventoryListeners.on(…)

Events listened to

  • c.a.c.o.OrderCompleted

  • c.a.c.o.OrderCanceled

Properties

  • acme.commerce.inventory.restock-threshold — c.a.c.c.Quantity. The threshold at which a InventoryEvents.StockShort is supposed to be triggered during inventory updates.

It consists of the following sections:

  • The application module’s base package.

  • The Spring beans exposed by the application module, grouped by stereotype. — In other words beans that are located in either the API package or any named interface package.

  • Exposed aggregate roots — Any entities that we find repositories for or explicitly declared as aggregate via jMolecules.

  • Application events published by the module — Those event types need to be demarcated using jMolecules @DomainEvent or implement its DomainEvent interface.

  • Application events listened to by the module — Derived from methods annotated with Spring’s @EventListener, @TransactionalEventListener, jMolecules' @DomainEventHandler or beans implementing ApplicationListener.

  • Configuration properties — Spring Boot Configuration properties exposed by the application module. Requires the usage of the spring-boot-configuration-processor artifact to extract the metadata attached to the properties.

7. Observing Application Modules

The interaction between application modules can be intercepted to create Micrometer spans to ultimately end up in traces you can visualize in tools like Zipkin. To activate the instrumentation add the following runtime dependency to your project:

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-observability</artifactId>
  <version>{projectVersion}</version>
  <scope>runtime</scope>
</dependency>

This will cause all Spring components that are part of the application module’s API being decorated with an aspect that will intercept invocations and create Micrometer spans for them. A sample invocation trace can be seen below:

observability
Figure 7. A sample module invocation trace

In this particular case, triggering the payment changes the state of the order which then causes an order completion event being triggered. This gets picked up asynchronously by the engine that triggers another state change on the order, works for a couple of seconds and triggers the final state change on the order in turn.

8. Appendix

Appendix A: Migrating from Moduliths

  • o.m.model.Modules has been renamed to o.s.m.model.ApplicationModules

  • o.m.model.ModuleDetectionStrategy has been renamed to o.s.m.model.ApplicationModuleDetectionStrategy

  • @o.m.test.ModuleTest has been renamed to @o.s.m.test.ApplicationModuleTest

  • o.m.docs.Documenter.Options has been renamed to o.s.m.docs.Documenter.DiagramOptions

  • The diagram style of component diagrams now defaults to DiagramStyle.C4 (override by calling DiagramOptions.withStyle(DiagramStyle.UML))

  • The module canvas hides non exposed types by default. To include application-module-internal types in the canvas, configure CanvasOptions to ….revealInternals().

  • The output folder for component diagrams and application module canvases has moved from moduliths-docs to spring-modulith-docs located in your build’s target folder (such as target for Maven).

Appendix B: Spring Modulith modules

Table 3. Spring Modulith starter POMs
Starter Typical scope Description

spring-modulith-starter-jdbc

compile

Includes spring-modulith-api, spring-modulith-moments as well as spring-modulith-events-jdbc.

spring-modulith-starter-jpa

compile

Includes spring-modulith-api, spring-modulith-moments as well as spring-modulith-events-jpa.

spring-modulith-starter-mongodb

compile

Includes spring-modulith-api, spring-modulith-moments as well as spring-modulith-events-mongodb.

spring-modulith-starter-test

compile

Includes spring-modulith-docs and spring-modulith-test.

Table 4. Individual Spring Modulith JARs
Module Typical scope Description

spring-modulith-api

compile

The abstractions to be used in your production code to customize Spring Modulith’s default behavior.

spring-modulith-core

runtime

The core application module model and API.

spring-modulith-docs

test

The Documenter API to create Asciidoctor and PlantUML documentation from the module model.

spring-modulith-events-core

runtime

The core implementation of the event publication registry as well as the integration abstractions EventPublicationRegistry and EventPublicationSerializer.

spring-modulith-events-jackson

runtime

A Jackson-based implementation of the EventPublicationSerializer.

spring-modulith-events-jdbc

runtime

A JDBC-based implementation of the EventPublicationRegistry.

spring-modulith-events-jpa

runtime

A JPA-based implementation of the EventPublicationRegistry.

spring-modulith-events-mongodb

runtime

A MongoDB-based implementation of the EventPublicationRegistry.

spring-modulith-moments

compile

The Passage of Time events implementation described here.

spring-modulith-observability

runtime

Observability infrastructure described here.

Appendix C: Event publication registry schemas

8.C.1. H2

CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
(
  ID               UUID NOT NULL,
  COMPLETION_DATE  TIMESTAMP(9) WITH TIME ZONE,
  EVENT_TYPE       VARCHAR(512) NOT NULL,
  LISTENER_ID      VARCHAR(512) NOT NULL,
  PUBLICATION_DATE TIMESTAMP(9) WITH TIME ZONE NOT NULL,
  SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
  PRIMARY KEY (ID)
)

8.C.2. HSQLDB

CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
(
  ID               UUID NOT NULL,
  COMPLETION_DATE  TIMESTAMP(9),
  EVENT_TYPE       VARCHAR(512) NOT NULL,
  LISTENER_ID      VARCHAR(512) NOT NULL,
  PUBLICATION_DATE TIMESTAMP(9) NOT NULL,
  SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
  PRIMARY KEY (ID)
)

8.C.3. MySQL

CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
(
  ID               VARCHAR(36) NOT NULL,
  LISTENER_ID      VARCHAR(512) NOT NULL,
  EVENT_TYPE       VARCHAR(512) NOT NULL,
  SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
  PUBLICATION_DATE TIMESTAMP(6) NOT NULL,
  COMPLETION_DATE  TIMESTAMP(6) DEFAULT NULL NULL,
  PRIMARY KEY (ID)
)

8.C.4. PostgreSQL

CREATE TABLE IF NOT EXISTS EVENT_PUBLICATION
(
  ID               UUID NOT NULL,
  LISTENER_ID      VARCHAR(512) NOT NULL,
  EVENT_TYPE       VARCHAR(512) NOT NULL,
  SERIALIZED_EVENT VARCHAR(4000) NOT NULL,
  PUBLICATION_DATE TIMESTAMP(9) WITH TIME ZONE NOT NULL,
  COMPLETION_DATE  TIMESTAMP(9) WITH TIME ZONE,
  PRIMARY KEY (ID)
)