0% found this document useful (0 votes)
35 views55 pages

SpringModulithFull

Uploaded by

enovka
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
35 views55 pages

SpringModulithFull

Uploaded by

enovka
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 55

10/29/24, 8:47 PM StackEdit

# Spring Modulith

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.

Overview

Spring Modulith is an opinionated toolkit to build domain-driven, modular applications


with Spring Boot. In the same way that Spring Boot has an opinion on the technical
arrangement of an application, Spring Modulith implements an opinion on how to
structure an app functionally and allows its individual, logical parts to interact with each
other. As a result, Spring Modulith enables developers to build applications that are
easier to update so they can accommodate changing business requirements over time.

Project Metadata

Version control github.com/spring-projects/spring-modulith

Bug tracker: github.com/spring-projects/spring-modulith

Release repository: Maven central

Milestone repository: repo.spring.io/milestone

Snapshot repository: repo.spring.io/snapshot

Javadoc: docs.spring.io/spring-modulith/docs/1.2.5/api

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

https://stackedit.io/app# 1/55
10/29/24, 8:47 PM StackEdit

Maven

Gradle

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-bom</artifactId>
<version>1.2.5</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.

Fundamentals

Application Modules
Customizing the Application Modules Arrangement

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.

Application Modules

In a Spring Boot application, an application module is a unit of functionality that consists


of the following parts:

https://stackedit.io/app# 2/55
10/29/24, 8:47 PM StackEdit

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.

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

Java

Kotlin

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

The modules will contain an in-memory representation of the application module


arrangement derived from the codebase. Which parts of that will be detected as a
module depends on the Java package structure underneath the package the class
pointed to resides in. Find out more about the arrangement expected by default in
Simple Application Modules. Advanced arrangements and customization options are
described in Advanced Application Modules and

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

https://stackedit.io/app# 3/55
10/29/24, 8:47 PM StackEdit

Writing the application module arrangement to the console

Java

Kotlin

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, the contained Spring components are identified, and
the respective visibility is rendered, too.

Excluding Packages

In case you would like to exclude certain Java classes or full packages from the
application module inspection, you can do so with:

Java

Kotlin

ApplicationModules.of(Application.class, JavaClass.Predicates.resideInAPack

Additional examples of exclusions:

com.example.db — Matches all files in the given package com.example.db .

https://stackedit.io/app# 4/55
10/29/24, 8:47 PM StackEdit

com.example.db.. — Matches all files in the given package ( com.example.db )


and all sub-packages ( com.example.db.a or com.example.db.b.c ).

..example.. — Matches a.example , a.example.b or a.b.example.c.d , but not


a.exam.b

Full details about possible matchers can be found in the JavaDoc of ArchUnit
PackageMatcher .

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 to 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-private


one).

A single inventory application module

Example
└─ src/main/java
├─ example
| └─ Application.java
└─ example.inventory
├─ InventoryManagement.java
└─ SomethingInventoryInternal.java

The application’s main package example .

An application module package inventory .

Advanced Application Modules

https://stackedit.io/app# 5/55
10/29/24, 8:47 PM StackEdit

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, is considered an
internal one. 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.

Open Application Modules

The arrangement described above are considered closed as they only expose types to
other modules that are actively selected for exposure. When applying Spring Modulith
to legacy applications, hiding all types located in nested packages from other modules
might be inadequate or require marking all those packages for exposure, too.

To turn an application module into an open one, use the @ApplicationModule


annotation on the package-info.java type.

Declaring an Application Modules as Open

Java

Kotlin

https://stackedit.io/app# 6/55
10/29/24, 8:47 PM StackEdit

@org.springframework.modulith.ApplicationModule(
type = Type.OPEN
)
package example.inventory;

Declaring an application module as open will cause the following changes to the
verification:

Access to application module internal types from other modules is generally


allowed.

All types, also ones residing in sub-packages of the application module base
package are added to the unnamed named interface, unless explicitly assigned to a
named interface.

This feature is intended to be primarily used with code bases of existing projects
gradually moving to the Spring Modulith recommended packaging structure. In a fully-
modularized application, using open application modules usually hints at sub-optimal
modularization and packaging structures.

Explicit Application Module Dependencies

A module can opt into declaring its allowed dependencies by using the
@ApplicationModule annotation on the package, represented through the package-
info.java file. As, for example, Kotlin lacks support for that file, you can also use the
annotation on a single type located in the application module’s root package.

Inventory explicitly configuring module dependencies

Java

Kotlin

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

https://stackedit.io/app# 7/55
10/29/24, 8:47 PM StackEdit

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 packages with @NamedInterface or a
type explicitly annotated with @org.springframework.modulith.PackageInfo .

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

Java

Kotlin

@org.springframework.modulith.NamedInterface("spi")
package example.order.spi;

The effect of that declaration is twofold: 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:

Defining allowed dependencies to dedicated named interfaces

Java

https://stackedit.io/app# 8/55
10/29/24, 8:47 PM StackEdit

Kotlin

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

If you wanted to express that an application module is allowed to refer to all explicitly
declared named interfaces, you can use the asterisk ( * ) as follows:

Using the asterisk to declare allowed dependencies to all declared named interfaces

Java

Kotlin

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

Customizing Module Detection

By default, application modules will be expected to be located in direct sub-packages of


the package the Spring Boot application class resides in. An alternative detection
strategy can be activated to only consider package explicitly annotated, either via Spring
Modulith’s @ApplicationModule or jMolecules @Module annotation. That strategy can
be activated by configuring the spring.modulith.detection-strategy to
explicitly-annotated .

Switching the application module detection strategy to only consider annotated


packages

spring.modulith.detection-strategy=explicitly-annotated

https://stackedit.io/app# 9/55
10/29/24, 8:47 PM StackEdit

If the neither default application module detection strategy nor the manually annotated
one 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:

Implementing a custom ApplicationModuleDetectionStrategy

Java

Kotlin

package example;

class CustomApplicationModuleDetectionStrategy implements ApplicationModule

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

This class can now be registered as spring.modulith.detection-strategy as follows:

spring.modulith.detection-strategy=example.CustomApplicationModuleDetection

Customizing the Application Modules Arrangement

Spring Moduliths allows to configure some core aspects around the application module
arrangement you create via the @Modulithic annotation to be used on the main Spring
Boot application class.

Java

https://stackedit.io/app# 10/55
10/29/24, 8:47 PM StackEdit

Kotlin

package example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.modulith.Modulithic;

@Modulithic
@SpringBootApplication
class MyApplication {

public static void main(String... args) {


SpringApplication.run(DemoApplication.class, args);
}
}

The annotation exposes the following attributes to customize:

Annotation attribute

Description

systemName

The human readable name of the application to be used in generated documentation.

sharedModules

Declares the application modules with the given names as shared modules, which means
that they will always be included in application module integration tests.

additionalPackages

Instructs Spring Modulith to treat the configured packages as additional root application
packages. In other words, application module detection will be triggered for those as
well.

Verifying Application Module Structure

https://stackedit.io/app# 11/55
10/29/24, 8:47 PM StackEdit

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

Java

Kotlin

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 a 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. Dependencies into internals of Open Application Modules are
allowed.

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 Domain-Driven Design verification rules described
here.

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:

https://stackedit.io/app# 12/55
10/29/24, 8:47 PM StackEdit

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

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

Java

Kotlin

@Service
@RequiredArgsConstructor
public class OrderManagement {

private final ApplicationEventPublisher events;


private final OrderInternal dependency;

@Transactional
https://stackedit.io/app# 13/55
10/29/24, 8:47 PM StackEdit

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

Java

Kotlin

@Component
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,

https://stackedit.io/app# 14/55
10/29/24, 8:47 PM StackEdit

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.

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

Java

Kotlin

@Component
class InventoryManagement {

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

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

Java

Kotlin

@Component
class InventoryManagement {

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

https://stackedit.io/app# 15/55
10/29/24, 8:47 PM StackEdit

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.

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. Automatic re-publication of the events can be enabled via the
spring.modulith.republish-outstanding-events-on-restart property.

https://stackedit.io/app# 16/55
10/29/24, 8:47 PM StackEdit

Figure 2. The transactional event listener arrangement after execution

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:

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.

https://stackedit.io/app# 17/55
10/29/24, 8:47 PM StackEdit

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.

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

Maven

Gradle

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-events-api</artifactId>
<version>1.2.5</version>
</dependency>

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

https://stackedit.io/app# 18/55
10/29/24, 8:47 PM StackEdit

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.

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 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 schema overview in the appendix.

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.

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:

https://stackedit.io/app# 19/55
10/29/24, 8:47 PM StackEdit

@Configuration
class MyConfiguration {

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

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 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 Fundamentals of Event Externalization.

Supported Infrastructure

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.

https://stackedit.io/app# 20/55
10/29/24, 8:47 PM StackEdit

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.

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

https://stackedit.io/app# 21/55
10/29/24, 8:47 PM StackEdit

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.

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. The key can be a SpEL expression which will get the event
instance configured as root object.

Defining a dynamic routing key via SpEL expression

Java

Kotlin

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

String getLastname() {
// …
}
}

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

https://stackedit.io/app# 22/55
10/29/24, 8:47 PM StackEdit

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

Java

Kotlin

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

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

Java

Kotlin

@Configuration
class ExternalizationConfiguration {

@Bean
EventExternalizationConfiguration eventExternalizationConfiguration() {

return EventExternalizationConfiguration.externalizing()
.select(EventExternalizationConfiguration.annotatedAsExternalized())
.mapping(SomeEvent.class, event -> …)
.routeKey(WithKeyProperty.class, WithKeyProperty::getKey)
.build();
}
}

We start by creating a default instance of EventExternalizationConfiguration .

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

https://stackedit.io/app# 23/55
10/29/24, 8:47 PM StackEdit

methods to easily select events by type, by packages, packages and annotation exist.
Also, a shortcut to define selection and routing in one step.

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.

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 published events

The following section describes a testing approach solely focused on tracking Spring
application events. For a more holistic approach on testing modules that use
@ApplicationModuleListener , please check out the 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

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

https://stackedit.io/app# 24/55
10/29/24, 8:47 PM StackEdit

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

Java

Kotlin

@ApplicationModuleTest
class OrderIntegrationTests {

@Test
void someTestMethod(AssertablePublishedEvents events) {

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

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

Integration Testing Application Modules

Spring Modulith allows to run integration tests bootstrapping individual application


modules in isolation or combination with others. To achieve this, add the Spring
Modulith test starter to your project like this

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-test</artifactId>
<scope>test</scope>
</dependency>

https://stackedit.io/app# 25/55
10/29/24, 8:47 PM StackEdit

and place a JUnit test class in an application module package or any sub-package of that
and annotate it with @ApplicationModuleTest :

An application module integration test class

Java

Kotlin

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 an application module integration test bootstrap

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.0-SNAPSHOT)

… - Bootstrapping @ApplicationModuleTest for example.order in mode STANDALO


… - =======================================================================
… - ## 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 …

https://stackedit.io/app# 26/55
10/29/24, 8:47 PM StackEdit

… - No active profile set, falling back to 1 default profile: "default"


… - Re-configuring auto-configuration and entity scan packages to: example.

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

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.

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

Java

Kotlin

@ApplicationModuleTest
class InventoryIntegrationTests {

https://stackedit.io/app# 27/55
10/29/24, 8:47 PM StackEdit

@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 domain events.

Defining Integration Test Scenarios

Integration testing application modules can become a quite elaborate effort. Especially if
the integration of those is based on asynchronous, transactional event handling, dealing
with the concurrent execution can be subject to subtle errors. Also, it requires dealing
with quite a few infrastructure components: TransactionOperations and
ApplicationEventProcessor to make sure events are published and delivered to
transactional listeners, Awaitility to handle concurrency and AssertJ assertions to
formulate expectations on the test execution’s outcome.

To ease the definition of application module integration tests, Spring Modulith provides
the Scenario abstraction that can be used by declaring it as test method parameter in
tests declared as @ApplicationModuleTest .

Using the Scenario API in a JUnit 5 test

Java

Kotlin

@ApplicationModuleTest
class SomeApplicationModuleTest {

@Test
public void someModuleIntegrationTest(Scenario scenario) {
// Use the Scenario API to define your integration test
}
}

The test definition itself usually follows the following skeleton:

https://stackedit.io/app# 28/55
10/29/24, 8:47 PM StackEdit

1. A stimulus to the system is defined. This is usually either an event publication or


an invocation of a Spring component exposed by the module.

2. Optional customization of technical details of the execution (timeouts, etc.)

3. The definition of some expected outcome, such as another application event being
fired that matches some criteria or some state change of the module that can be
detected by invoking exposed components.

4. Optional, additional verifications made on the received event or observed,


changed state.

Scenario exposes an API to define these steps and guide you through the definition.

Defining a stimulus as starting point of the Scenario

Java

Kotlin

// Start with an event publication


scenario.publish(new MyApplicationEvent(…)).…

// Start with a bean invocation


scenario.stimulate(() -> someBean.someMethod(…)).…

Both the event publication and bean invocation will happen within a transaction
callback to make sure the given event or any ones published during the bean invocation
will be delivered to transactional event listeners. Note, that this will require a new
transaction to be started, no matter whether the test case is already running inside a
transaction or not. In other words, state changes of the database triggered by the
stimulus will never be rolled back and have to be cleaned up manually. See the
….andCleanup(…) methods for that purpose.

The resulting object can now get the execution customized though the generic
….customize(…) method or specialized ones for common use cases like setting a
timeout ( ….waitAtMost(…) ).

The setup phase will be concluded by defining the actual expectation of the outcome of
the stimulus. This can be an event of a particular type in turn, optionally further
constraint by matchers:

https://stackedit.io/app# 29/55
10/29/24, 8:47 PM StackEdit

Expecting an event being published as operation result

Java

Kotlin

….andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …) // Use some predicate here
.…

These lines set up a completion criteria that the eventual execution will wait for to
proceed. In other words, the example above will cause the execution to eventually block
until either the default timeout is reached or a SomeOtherEvent is published that
matches the predicate defined.

The terminal operations to execute the event-based Scenario are named ….toArrive…
() and allow to optionally access the expected event published, or the result object of
the bean invocation defined in the original stimulus.

Triggering the verification

Java

Kotlin

// Executes the scenario


….toArrive(…)

// Execute and define assertions on the event received


….toArriveAndVerify(event -> …)

The choice of method names might look a bit weird when looking at the steps
individually but they actually read quite fluent when combined.

A complete Scenario definition

Java

Kotlin

scenario.publish(new MyApplicationEvent(…))
.andWaitForEventOfType(SomeOtherEvent.class)

https://stackedit.io/app# 30/55
10/29/24, 8:47 PM StackEdit

.matching(event -> …)
.toArriveAndVerify(event -> …);

Alternatively to an event publication acting as expected completion signal, we can also


inspect the state of the application module by invoking a method on one of the
components exposed. The scenario would then rather look like this:

Expecting a state change

Java

Kotlin

scenario.publish(new MyApplicationEvent(…))
.andWaitForStateChange(() -> someBean.someMethod(…)))
.andVerify(result -> …);

The result handed into the ….andVerify(…) method will be the value returned by the
method invocation to detect the state change. By default, non- null values and non-
empty Optional s will be considered a conclusive state change. This can be tweaked by
using the ….andWaitForStateChange(…, Predicate) overload.

Customizing Scenario Execution

To customize the execution of an individual scenario, call the ….customize(…) method


in the setup chain of the Scenario :

Customizing a Scenario execution

Java

Kotlin

scenario.publish(new MyApplicationEvent(…))
.customize(conditionFactory -> conditionFactory.atMost(Duration.ofSeconds
.andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …)
.toArriveAndVerify(event -> …);

To globally customize all Scenario instances of a test class, implement a


ScenarioCustomizer and register it as JUnit extension.

https://stackedit.io/app# 31/55
10/29/24, 8:47 PM StackEdit

Registering a ScenarioCustomizer

Java

Kotlin

@ExtendWith(MyCustomizer.class)
class MyTests {

@Test
void myTestCase(Scenario scenario) {
// scenario will be pre-customized with logic defined in MyCustomizer
}

static class MyCustomizer implements ScenarioCustomizer {

@Override
Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Metho
return conditionFactory -> …;
}
}
}

Moments — a Passage of Time Events API

spring-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:

Maven

Gradle

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

https://stackedit.io/app# 32/55
10/29/24, 8:47 PM StackEdit

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 .

Java

Kotlin

@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

https://stackedit.io/app# 33/55
10/29/24, 8:47 PM StackEdit

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.

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

https://stackedit.io/app# 34/55
10/29/24, 8:47 PM StackEdit

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

Additionally, Documenter can produce an aggregating Asciidoc file that includes all
existing component diagrams and canvases.

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

Java

Kotlin

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.

All modules and their relationships rendered as C4 component diagram

Figure 1. 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 the canvas.

https://stackedit.io/app# 35/55
10/29/24, 8:47 PM StackEdit

A subset of application modules and their relationships starting from the order
module rendered as C4 component diagram

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

Using Traditional UML Component Diagrams

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

Java

Kotlin

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

This will cause the diagrams to look like this:

All modules and their relationships rendered as UML component diagram

Figure 3. All modules and their relationships rendered as UML component diagram

A subset of application modules and their relationships starting from the order
module rendered as UML component diagram

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

Generating Application Module Canvases

The Application Module Canvases can be generated by calling


Documenter.writeModuleCanvases() :

Generating application module canvases using Documenter

Java

Kotlin

https://stackedit.io/app# 36/55
10/29/24, 8:47 PM StackEdit

class DocumentationTests {

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

@Test
void writeDocumentationSnippets() {

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

By default, the documentation will be generated to spring-modulith-docs folder in


your build system’s build folder. A generated canvas looks like this:

Table 1. 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

https://stackedit.io/app# 37/55
10/29/24, 8:47 PM StackEdit

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. This will detect component stereotypes defined by jMolecules
architecture abstractions, but also standard Spring stereotype annotations.

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.

https://stackedit.io/app# 38/55
10/29/24, 8:47 PM StackEdit

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.

Generating an Aggregating Document

When using Documenter.writeDocumentation(…) an all-docs.adoc file will be


generated, linking all generated diagrams and Application Module Canvases. We can
manually generate the aggregating document by calling
Documenter.writeAggregatingDocument() :

Generating an aggregating document using Documenter

Java

Kotlin

class DocumentationTests {

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

@Test
void writeDocumentationSnippets() {

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

The aggregating document will include any existing application module component
diagrams and application module canvases. If there are none, then this method will not
produce an output file.

Spring Modulith Runtime Support


https://stackedit.io/app# 39/55
10/29/24, 8:47 PM StackEdit

The functionality described in previous chapters have all used the application module
arrangement in either testing scenarios for verification and documentation purposes or
were general support functionality that help to loosely couple modules but did not work
with the application module structure directly. In this section we are going to describe
Spring Modulith’s support for module initialization at application runtime.

Setting up Runtime Support for Application Modules

To enable the runtime support for Spring Modulith, make sure you include the spring-
modulith-runtime JAR in your project.

Maven

Gradle

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-runtime</artifactId>
<scope>runtime</scope>
</dependency>

It’s worth noting that using the runtime support of Spring Modulith will mean that you
include both ArchUnit and the JGraphT (required to topologically sort application
modules) library in your application.

Adding this JAR will cause Spring Boot auto-configuration to run that registers the
following components in your application:

An ApplicationModulesRuntime that allows to access the ApplicationModules .

A SpringBootApplicationRuntime to back the former bean to detect the main


application class.

An event listener for ApplicationStartedEvent s that will invoke


ApplicationModuleInitializer beans defined in the application context.

Application Module Initializers

https://stackedit.io/app# 40/55
10/29/24, 8:47 PM StackEdit

When working with application modules, it is pretty common to need to execute some
code specific to an individual module on application startup. This means that the
execution order of that code needs to follow the dependency structure of the
application modules. If a module B depends on module A, the initialization code of A has
to run before the one for B, even if the initializers do not directly depend on another.

Diagram

While developers could of course define the execution order via Spring’s standard
@Order annotation or Ordered interface, Spring Modulith provides an
ApplicationModuleInitializer interface for beans to be run on application startup.
The execution order of those beans will automatically follow the application module
dependency structure.

Java

Kotlin

@Component
class MyInitializer implements ApplicationModuleInitializer {

@Override
public void initialize() {
// Initialization code goes here
}
}

Note that the ApplicationModuleInitializer beans will only be invoked if the


spring-modulith-runtime JAR is on the classpath (see Setting up Runtime Support for
Application Modules) as that pulls in the dependencies that are needed to topologically
sort the initializers according to the application module structure.

Production-ready Features

Spring Modulith provides support to expose architectural information about your


system as a Spring Boot actuator endpoint as well as observing the interaction between
application modules by capturing metrics and traces. As a production-ready application

https://stackedit.io/app# 41/55
10/29/24, 8:47 PM StackEdit

is likely to require both, the most convenient way to activate those features is to use the
Spring Modulith Insight starter as follows:

Using the Spring Modulith Insight starter

Maven

Gradle

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-insight</artifactId>
<version>1.2.5</version>
<scope>runtime</scope>
</dependency>

This will include the actuator and observability support as well as Spring Boot’s actuator
startup for general support for actuators. Note, that you will still have to add further
dependencies to connect your application to your monitoring tools such as Zipkin,
Wavefront etc. usually via OpenTelemetry or Brave. Find more information on that in the
corresponding section of Spring Boot’s reference documentation.

Application Module Actuator

The application module structure can be exposed as Spring Boot actuator. To enable the
actuator, add the spring-modulith-actuator dependency to the project:

Using the Spring Modulith actuator support

Maven

Gradle

<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-actuator</artifactId>
<version>1.2.5</version>
<scope>runtime</scope>
</dependency>

<!-- Spring Boot actuator starter required to enable actuators in general -

https://stackedit.io/app# 42/55
10/29/24, 8:47 PM StackEdit

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>…</version>
<scope>runtime</scope>
</dependency>

Running the application will now expose an modulith actuator resource:

Accessing the actuator HTTP resource

GET http://localhost:8080/actuator

{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"modulith": {
"href": "http://localhost:8080/actuator/modulith",
"templated": false
}
}
}

The modulith actuator resource advertised.

The modulith resource adheres to the following structure:

Table 1. The JSON structure of the application modules actuator

JSONPath

Description

$.{moduleName}

https://stackedit.io/app# 43/55
10/29/24, 8:47 PM StackEdit

The technical name of an application module. Target for module references in


dependencies.target .

$.{moduleName}.displayName

The human-readable name of the application module.

$.{moduleName}.basePackage

The application module’s base package.

$.{moduleName}.dependencies[]

All outgoing dependencies of the application module

$.{moduleName}.dependencies[].target

The name of the application module depended on. A reference to a {moduleName} .

$.{moduleName}.dependencies[].types[]

The types of dependencies towards the target module. Can either be DEFAULT (simple
type dependency), USES_COMPONENT (Spring bean dependency) or EVENT_LISTENER .

An example module arrangement would look like this:

An example response for the application modules actuator

{
"a": {
"basePackage": "example.a",
"displayName": "A",
"dependencies": []
},
"b": {
"basePackage": "example.b",
"displayName": "B",
"dependencies": [ {
"target": "a",
"types": [ "EVENT_LISTENER", "USES_COMPONENT" ]
} ]
}
}

https://stackedit.io/app# 44/55
10/29/24, 8:47 PM StackEdit

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:

Using the Spring Modulith observability support

Maven

Gradle

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

You will have to configure additional infrastructure dependencies depending on the


tooling you want to pipe the observability metadata in. For details, please check the
corresponding Spring Boot documentation on which dependencies to include for your
setup.

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:

Figure 1. A sample module invocation trace

https://stackedit.io/app# 45/55
10/29/24, 8:47 PM StackEdit

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.

Appendix

Appendix A: Spring Modulith Configuration Properties

Property

Default value

Description

spring.modulith.default-async-termination

true

Whether to configure defaults for the async processing termination, namely to wait for
task completion for 2 seconds. See TaskExecutionProperties for details.

spring.modulith.detection-strategy

none

The strategy to be applied to detect application modules. Can either be the class name
of a custom implementation of ApplicationModuleDetectionStrategy or direct-
subpackages (which is also the final fallback if nothing is configured) or explicitly-
annotated to only select packages explicitly annotated with @ApplicationModule or
jMolecules’ @Module . See Customize Application Module Detection for details.

spring.modulith.events.externalization.enabled

true

Whether to enable event externalization.

https://stackedit.io/app# 46/55
10/29/24, 8:47 PM StackEdit

spring.modulith.events.jdbc.schema-initialization.enabled

false

Whether to initialize the JDBC event publication schema.

spring.modulith.events.kafka.enable-json

true

Whether to enable JSON support for KafkaTemplate .

spring.modulith.events.mongodb.transaction-management.enabled

true

Whether to automatically enable transactions for MongoDB. Requires the database to


be run with a replica set.

spring.modulith.events.neo4j.event-index.enabled

false

Whether to create indexes on the .

spring.modulith.events.rabbitmq.enable-json

true

Whether to enable JSON support for RabbitTemplate .

spring.modulith.moments.enableTimeMachine

false

Whether to enable the TimeMachine .

spring.modulith.moments.granularity

HOURS

The granularity of events to publish. ( HOURS , DAYS )

spring.modulith.moments.locale

https://stackedit.io/app# 47/55
10/29/24, 8:47 PM StackEdit

Locale.getDefault()

The Locale to use when determining week boundaries.

spring.modulith.moments.zoneId

ZoneOffset.UTC

The timezone of the dates for the events being published.

spring.modulith.republish-outstanding-events-on-restart

false

Whether to republish outstanding event publications on restarts of the application.


Usually not recommended in multi-instance deployments as other instances might still
be processing events.

Appendix B: Spring Modulith modules

Table 1. Spring Modulith starter POMs

Starter

Typical scope

Includes

spring-modulith-starter-core

compile

spring-modulith-api

spring-modulith-moments

spring-modulith-core (runtime)

spring-modulith-runtime (runtime)

spring-modulith-starter-insight

https://stackedit.io/app# 48/55
10/29/24, 8:47 PM StackEdit

runtime

spring-modulith-actuator (runtime)

spring-modulith-observability (runtime)

spring-boot-starter-actuator (runtime)

spring-modulith-starter-jdbc

compile

spring-modulith-starter-core

spring-modulith-events-api

spring-modulith-events-core (runtime)

spring-modulith-events-jdbc (runtime)

spring-modulith-events-jackson (runtime)

spring-modulith-starter-jpa

compile

spring-modulith-starter-core

spring-modulith-events-api

spring-modulith-events-core (runtime)

spring-modulith-events-jpa (runtime)

spring-modulith-events-jackson (runtime)

spring-modulith-starter-mongodb

compile

spring-modulith-starter-core

spring-modulith-events-api

spring-modulith-events-core (runtime)

https://stackedit.io/app# 49/55
10/29/24, 8:47 PM StackEdit

spring-modulith-events-mongodb (runtime)

spring-modulith-events-jackson (runtime)

spring-modulith-starter-neo4j

compile

spring-modulith-starter-core

spring-modulith-events-api

spring-modulith-events-core (runtime)

spring-modulith-events-neo4j (runtime)

spring-modulith-events-jackson (runtime)

spring-modulith-starter-test

test

spring-modulith-docs

spring-modulith-test

Table 2. Individual Spring Modulith JARs

Module

Typical scope

Description

spring-modulith-actuator

runtime

A Spring Boot actuator to expose the application module structure via an actuator.

spring-modulith-api

compile

https://stackedit.io/app# 50/55
10/29/24, 8:47 PM StackEdit

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-amqp

runtime

Event externalization support for AMQP.

spring-modulith-events-api

runtime

API to customize the event features of Spring Modulith.

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

https://stackedit.io/app# 51/55
10/29/24, 8:47 PM StackEdit

A JDBC-based implementation of the EventPublicationRegistry .

spring-modulith-events-jms

runtime

Event externalization support for JMS.

spring-modulith-events-jpa

runtime

A JPA-based implementation of the EventPublicationRegistry .

spring-modulith-events-kafka

runtime

Event externalization support for Kafka.

spring-modulith-events-mongodb

runtime

A MongoDB-based implementation of the EventPublicationRegistry .

spring-modulith-events-neo4j

runtime

A Neo4j-based implementation of the EventPublicationRegistry .

spring-modulith-junit

test

Test execution optimizations based on the application module structure. Find more
details here.

spring-modulith-moments

compile

The Passage of Time events implementation described here.

https://stackedit.io/app# 52/55
10/29/24, 8:47 PM StackEdit

spring-modulith-observability

runtime

Observability infrastructure described here.

spring-modulith-runtime

runtime

Support to bootstrap an ApplicationModules instance at runtime. Usually not directly


depended on but transitively used by spring-modulith-actuator and spring-
modulith-observability .

spring-modulith-test

test

Integration testing support. Find more details here.

Appendix C: Event publication registry schemas

The JDBC-based event publication registry support expects the following database
schemas to be present in the database. If you would like Spring Modulith to create the
schema for you, set the application property spring.modulith.events.jdbc-schema-
initialization.enabled to true .

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

https://stackedit.io/app# 53/55
10/29/24, 8:47 PM StackEdit

CREATE INDEX IF NOT EXISTS EVENT_PUBLICATION_BY_LISTENER_ID_AND_SERIALIZED_


CREATE INDEX IF NOT EXISTS EVENT_PUBLICATION_BY_COMPLETION_DATE_IDX ON EVEN

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)
);
CREATE INDEX IF NOT EXISTS EVENT_PUBLICATION_BY_LISTENER_ID_AND_SERIALIZED_
CREATE INDEX IF NOT EXISTS EVENT_PUBLICATION_BY_COMPLETION_DATE_IDX ON EVEN

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),
INDEX EVENT_PUBLICATION_BY_COMPLETION_DATE_IDX (COMPLETION_DATE)
);

PostgreSQL

CREATE TABLE IF NOT EXISTS event_publication


(
id UUID NOT NULL,
listener_id TEXT NOT NULL,
event_type TEXT NOT NULL,
serialized_event TEXT NOT NULL,

https://stackedit.io/app# 54/55
10/29/24, 8:47 PM StackEdit

publication_date TIMESTAMP WITH TIME ZONE NOT NULL,


completion_date TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS event_publication_serialized_event_hash_idx ON e
CREATE INDEX IF NOT EXISTS event_publication_by_completion_date_idx ON even

Appendix D: 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).

https://stackedit.io/app# 55/55

You might also like