SpringModulithFull
SpringModulithFull
# 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
Project Metadata
Javadoc: docs.spring.io/spring-modulith/docs/1.2.5/api
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:
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
Application Modules
https://stackedit.io/app# 2/55
10/29/24, 8:47 PM StackEdit
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.
Java
Kotlin
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
Java
Kotlin
modules.forEach(System.out::println);
## 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
https://stackedit.io/app# 4/55
10/29/24, 8:47 PM StackEdit
Full details about possible matchers can be found in the JavaDoc of ArchUnit
PackageMatcher .
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.
Example
└─ src/main/java
├─ example
| └─ Application.java
└─ example.inventory
├─ InventoryManagement.java
└─ SomethingInventoryInternal.java
https://stackedit.io/app# 5/55
10/29/24, 8:47 PM StackEdit
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.
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.
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:
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.
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.
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
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:
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;
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.
Java
Kotlin
package example;
@Override
public Stream<JavaPackage> getModuleBasePackages(JavaPackage basePackage)
// Your module detection goes here
}
}
spring.modulith.detection-strategy=example.CustomApplicationModuleDetection
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 {
Annotation attribute
Description
systemName
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.
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();
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.
Spring Modulith optionally integrates with the jMolecules ArchUnit library and, if
present, automatically triggers its Domain-Driven Design verification rules described
here.
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).
https://stackedit.io/app# 12/55
10/29/24, 8:47 PM StackEdit
Java
Kotlin
@Service
@RequiredArgsConstructor
public class OrderManagement {
@Transactional
public void complete(Order 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.
Java
Kotlin
@Service
@RequiredArgsConstructor
public class OrderManagement {
@Transactional
https://stackedit.io/app# 13/55
10/29/24, 8:47 PM StackEdit
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.
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.
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.
Java
Kotlin
@Component
class InventoryManagement {
@ApplicationModuleListener
void on(OrderCompleted event) { /* … */ }
}
https://stackedit.io/app# 15/55
10/29/24, 8:47 PM StackEdit
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.
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
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
JDBC
spring-modulith-starter-jdbc
https://stackedit.io/app# 17/55
10/29/24, 8:47 PM StackEdit
MongoDB
spring-modulith-starter-mongodb
Neo4j
spring-modulith-starter-neo4j
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:
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
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.
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
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.
The event externalization performs three steps on each application event published.
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.
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.
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:
Java
Kotlin
@Externalized("…::#{@beanName.someMethod(#this)}")
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 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.
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.
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
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.
<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 :
Java
Kotlin
package example.order;
@ApplicationModuleTest
class OrderIntegrationTests {
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:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.0-SNAPSHOT)
https://stackedit.io/app# 26/55
10/29/24, 8:47 PM StackEdit
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
ALL_DEPENDENCIES — Runs the current module and the entire tree of modules
depended on.
Java
Kotlin
@ApplicationModuleTest
class InventoryIntegrationTests {
https://stackedit.io/app# 27/55
10/29/24, 8:47 PM StackEdit
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.
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 .
Java
Kotlin
@ApplicationModuleTest
class SomeApplicationModuleTest {
@Test
public void someModuleIntegrationTest(Scenario scenario) {
// Use the Scenario API to define your integration test
}
}
https://stackedit.io/app# 28/55
10/29/24, 8:47 PM StackEdit
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.
Scenario exposes an API to define these steps and guide you through the definition.
Java
Kotlin
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
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.
Java
Kotlin
The choice of method names might look a bit weird when looking at the steps
individually but they actually read quite fluent when combined.
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 -> …);
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.
Java
Kotlin
scenario.publish(new MyApplicationEvent(…))
.customize(conditionFactory -> conditionFactory.atMost(Duration.ofSeconds
.andWaitForEventOfType(SomeOtherEvent.class)
.matching(event -> …)
.toArriveAndVerify(event -> …);
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
}
@Override
Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Metho
return conditionFactory -> …;
}
}
}
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:
Java
Kotlin
@Configuration
class MyConfiguration {
@Bean
Clock myCustomClock() {
// Create a custom Clock here
}
}
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()
spring.modulith.moments.quarter-start-month
Months.JANUARY
spring.modulith.moments.zone-id
ZoneOffset#UTC
The ZoneId to determine times which are attached to the events published.
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:
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.
Java
Kotlin
class DocumentationTests {
@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.
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
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);
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
Java
Kotlin
https://stackedit.io/app# 36/55
10/29/24, 8:47 PM StackEdit
class DocumentationTests {
@Test
void writeDocumentationSnippets() {
new Documenter(modules)
.writeModuleCanvases();
}
}
Base package
com.acme.commerce.inventory
Spring components
Services
c.a.c.i.InventoryManagement
Repositories
c.a.c.i.Inventory
Event listeners
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.InventoryItem.decreaseQuantity(…)
c.a.c.i.InternalInventoryListeners.on(…)
Events listened to
c.a.c.o.OrderCompleted
c.a.c.o.OrderCanceled
Properties
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.
https://stackedit.io/app# 38/55
10/29/24, 8:47 PM StackEdit
Java
Kotlin
class DocumentationTests {
@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.
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.
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:
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
}
}
Production-ready Features
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:
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.
The application module structure can be exposed as Spring Boot actuator. To enable the
actuator, add the spring-modulith-actuator dependency to the project:
Maven
Gradle
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-actuator</artifactId>
<version>1.2.5</version>
<scope>runtime</scope>
</dependency>
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>
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
}
}
}
JSONPath
Description
$.{moduleName}
https://stackedit.io/app# 43/55
10/29/24, 8:47 PM StackEdit
$.{moduleName}.displayName
$.{moduleName}.basePackage
$.{moduleName}.dependencies[]
$.{moduleName}.dependencies[].target
$.{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 .
{
"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
Maven
Gradle
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-observability</artifactId>
<version>1.2.5</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:
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
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
https://stackedit.io/app# 46/55
10/29/24, 8:47 PM StackEdit
spring.modulith.events.jdbc.schema-initialization.enabled
false
spring.modulith.events.kafka.enable-json
true
spring.modulith.events.mongodb.transaction-management.enabled
true
spring.modulith.events.neo4j.event-index.enabled
false
spring.modulith.events.rabbitmq.enable-json
true
spring.modulith.moments.enableTimeMachine
false
spring.modulith.moments.granularity
HOURS
spring.modulith.moments.locale
https://stackedit.io/app# 47/55
10/29/24, 8:47 PM StackEdit
Locale.getDefault()
spring.modulith.moments.zoneId
ZoneOffset.UTC
spring.modulith.republish-outstanding-events-on-restart
false
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
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
spring-modulith-core
runtime
spring-modulith-docs
test
The Documenter API to create Asciidoctor and PlantUML documentation from the
module model.
spring-modulith-events-amqp
runtime
spring-modulith-events-api
runtime
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
spring-modulith-events-jdbc
runtime
https://stackedit.io/app# 51/55
10/29/24, 8:47 PM StackEdit
spring-modulith-events-jms
runtime
spring-modulith-events-jpa
runtime
spring-modulith-events-kafka
runtime
spring-modulith-events-mongodb
runtime
spring-modulith-events-neo4j
runtime
spring-modulith-junit
test
Test execution optimizations based on the application module structure. Find more
details here.
spring-modulith-moments
compile
https://stackedit.io/app# 52/55
10/29/24, 8:47 PM StackEdit
spring-modulith-observability
runtime
spring-modulith-runtime
runtime
spring-modulith-test
test
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
https://stackedit.io/app# 53/55
10/29/24, 8:47 PM StackEdit
HSQLDB
MySQL
PostgreSQL
https://stackedit.io/app# 54/55
10/29/24, 8:47 PM StackEdit
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