Spring Modulith Documentation
repository·main·Indexed 22 days ago
https://github.com/spring-projects/spring-modulithTools for building modular Spring Boot applications. Spring Modulith enables the definition of application modules with enforced boundaries between public API packages and internal packages. It provides features for verifying modular structure via ApplicationModules, isolated integration testing with @ApplicationModuleTest, automatic documentation generation, and a reliable domain event system using @EnablePersistentDomainEvents and the EventPublicationRegistry.
What's inside Spring Modulith
- Spring Modulith is an opinionated toolkit designed for building domain-driven, modular applications using Spring Boot. It provides a structured approach to functional application organization, defining how individual logical parts of an application should be structured and how they are permitted to interact. This helps developers build applications that are easier to maintain and update as business requirements evolve.
Explore Spring Modulith example projects
mainThe repository contains several example projects demonstrating different Spring Modulith capabilities using a consistent domain model (an
ordermodule and aninventorymodule):spring-modulith-example-full: Demonstrates fundamental module setup, including:- Modularity verification using
ModularityTests.verifiesModularStructure(). - Documentation generation using
ModularityTests.createModuleDocumentation(). - The Event Publication Registry (EPR) and its ability to track incomplete event publications (demonstrated via
FailingAsyncTransactionalEventListener). - Integration testing using the
ScenarioAPIs inOrderIntegrationTests.
- Modularity verification using
spring-modulith-example-epr-jdbc: Demonstrates the Event Publication Registry implementation using Spring Data JDBC (seeApplicationIntegrationTests).spring-modulith-example-epr-mongodb: Demonstrates the Event Publication Registry implementation using MongoDB (seeApplicationIntegrationTests).spring-modulith-example-outbox: Demonstrates the outbox pattern for event externalization, where events are persisted to an outbox table within the same transaction and processed asynchronously.
How Spring Modulith application modules work
mainSpring Modulith introduces the concept of application modules to help align code structure with the domain.
By default, an application module consists of:
- An API package: These are packages located directly under the application's main package (e.g.,
example.inventoryandexample.order). Types in these packages are considered part of the module's public interface. - Internal packages: These are nested sub-packages (e.g.,
example.order.internal). Types within these packages are considered internal and are inaccessible to code residing in other modules.
This allows you to use standard Java visibility modifiers for simple cases, but Spring Modulith provides the enforcement layer to prevent illegal access to nested internal packages that would otherwise be accessible in plain Java.
- An API package: These are packages located directly under the application's main package (e.g.,
Decouple modules using Application Events
mainTo maintain high decoupling between application modules, use Spring's
ApplicationEventPublisherinstead of direct bean dependencies. This prevents 'functional gravity' where a service attracts too many dependencies from other modules, making it harder to test and maintain.Instead of injecting a service from another module, publish a domain event once the state transition in the primary aggregate is complete.
@Service @RequiredArgsConstructor public class OrderManagement { private final ApplicationEventPublisher events; private final OrderInternal dependency; @Transactional public void complete(Order order) { // State transition on the order aggregate go here events.publishEvent(new OrderCompleted(order.getId())); } }How application modules work in Spring Modulith
mainAn application module is a unit of functionality consisting of:
- Provided Interface (API): Spring beans and application events exposed to other modules.
- Internal Implementation: Components not intended for access by other modules.
- Required Interface: Dependencies on other modules' APIs (beans, events, or configuration properties).
Module Arrangement Strategies
Simple Modules
By default, each direct sub-package of the main package (where
@SpringBootApplicationresides) is an application module. In a simple module, the module's API consists of all public types in that package. You can hide implementation details using Java's package-private scope.Advanced Modules
If a module package contains sub-packages, the base package is treated as the API package. Sub-packages (e.g.,
example.order.internal) are considered internal. While types in sub-packages might bepublicto allow internal module access, Spring Modulith prevents other modules from accessing them.Nested Modules
You can define nested modules by annotating a package with
@ApplicationModule(typically in apackage-info.javafile).- Access Rules: Nested modules can access code in their parent modules (including internal code). However, code in a parent module cannot access the nested module unless it is part of a named interface.
Open Modules
For legacy applications, you can turn a module into an "open" one using
@ApplicationModule(type = Type.OPEN). This allows access to internal types from other modules and treats all sub-packages as part of the unnamed named interface. This is intended for gradual migration to a modular structure.How Kafka event externalization works
mainThe externalization process follows this lifecycle:
- Bootstrap: On application startup, the
spring-modulith-events-kafkamodule registers anApplicationModuleListenerspecifically for externalization. - Event Publication: When a business method publishes an event (e.g.,
OrderCompleted) that is annotated with@Externalized, the infrastructure detects it. - Registry Tracking: The Event Publication Registry creates a record to track the event's processing.
- Asynchronous Externalization: An asynchronous task (e.g.,
task-1) is triggered to handle the externalization via the registeredApplicationModuleListener. - Kafka Dispatch: The listener invokes the Kafka operations to send the message.
- Completion: Once the message is successfully sent, the Event Publication Registry marks the publication as completed.
- Bootstrap: On application startup, the
How the Documenter abstraction works
mainThe
Documenterabstraction is used to generate documentation snippets from anApplicationModulesinstance. It can produce several types of documentation for inclusion in Asciidoc files:- C4 and UML component diagrams: Visual representations of relationships between application modules.
- Application Module Canvas: A tabular overview of a module's contents, including Spring beans, aggregate roots, published/listened events, and configuration properties.
- Aggregating Document: An
all-docs.adocfile that links all existing diagrams and canvases together.
To use it, you typically pass an
ApplicationModulesinstance (obtained viaApplicationModules.of(Application.class)) to theDocumenterconstructor.ApplicationModules modules = ApplicationModules.of(Application.class); new Documenter(modules).writeModulesAsPlantUml();How Moments and TimeMachine work
mainMoments provides a mechanism to decouple business logic from time-based triggers (like Spring Scheduling) by publishing specific temporal events.
Published Events
Moments automatically publishes the following events:
DayHasPassedWeekHasPassedMonthHasPassedQuarterHasPassedYearHasPassed
The Moments Bean
By default, Moments registers a bean of type
Moments. This bean is responsible for publishing the events listed above.The TimeMachine Bean
If you enable the time machine feature, the application will expose a bean of type
TimeMachine(which extendsMoments) instead.TimeMachineprovides a.shift(Duration)method, allowing you to manually move "now" forward by a specific duration. Moving time forward triggers all events that would have occurred during that time delta.// If enable-time-machine is true, you can inject TimeMachine @Autowired private TimeMachine timeMachine; public void simulateOneDay() { timeMachine.shift(Duration.ofDays(1)); }Configure Application-Module Aware Flyway Migrations
mainSpring Modulith (2.0+) supports executing module-specific Flyway migrations. Modules should define migrations for their own persistent data only, executed in the order of the module dependency tree.
To use this, activate the
spring.modulith.runtime.flyway-enabledconfiguration property.Migration Organization:
- Root Migrations: Place in
db/migration/__root. These use the default version tracking table. - Module Migrations: Place in
db/migration/$moduleIdentifier. These use a specific tracking table namedflyway_schema_history_$moduleIdentifierand are set to a baseline version of 0. - Wildcards: Migration locations ending in a wildcard are not customized.
Important: Version numbers in migration scripts are scoped to the application module; do not use global ordering across modules.
- Root Migrations: Place in
How reliable domain events work in Spring Modulith
mainSpring Modulith provides a mechanism for reliable domain events by ensuring that events published within a transaction are not lost if the application fails during the notification of
@TransactionalEventListeners.Instead of relying solely on Spring's in-memory
ApplicationEventPublisher, this system uses a transactional datastore to record the publication of events. If an event listener fails or the application crashes before a listener completes, the event remains in theEventPublicationRegistry. These pending events can then be re-published upon application restart or via a scheduled process, ensuring eventual consistency.How the Event Publication Registry works
mainThe Event Publication Registry hooks into Spring's event mechanism to ensure reliability. When an event is published, the registry writes an entry into an event publication log as part of the original business transaction.
Transactional event listeners (including those annotated with
@ApplicationModuleListener) are wrapped in an aspect that marks the log entry asCOMPLETEDupon successful execution. If the listener fails, the log entry remains, allowing for retry mechanisms.By default, all listeners annotated with
@TransactionalEventListenerare tracked. You can customize this via thespring.modulith.events.registry-trigger-annotationproperty.Configure application module test bootstrap modes
mainThe
@ApplicationModuleTestannotation allows you to control which modules are included in the SpringApplicationContextduring the test run using different bootstrap modes:STANDALONE(default): Runs only the current module.DIRECT_DEPENDENCIES: Runs the current module and all modules it directly depends on.ALL_DEPENDENCIES: Runs the current module and the entire tree of all depended-on modules.