Spring Modulith Documentation

repository·main·Indexed 22 days ago

https://github.com/spring-projects/spring-modulith

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

Tokens
19.6K
Snippets
55
Records
88
Agent score
78%

What's inside Spring Modulith

  1. What is Spring Modulith?

    main
    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.
  2. Explore Spring Modulith example projects

    main

    The repository contains several example projects demonstrating different Spring Modulith capabilities using a consistent domain model (an order module and an inventory module):

    • 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 Scenario APIs in OrderIntegrationTests.
    • spring-modulith-example-epr-jdbc: Demonstrates the Event Publication Registry implementation using Spring Data JDBC (see ApplicationIntegrationTests).
    • spring-modulith-example-epr-mongodb: Demonstrates the Event Publication Registry implementation using MongoDB (see ApplicationIntegrationTests).
    • 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.
  3. How Spring Modulith application modules work

    main

    Spring Modulith introduces the concept of application modules to help align code structure with the domain.

    By default, an application module consists of:

    1. An API package: These are packages located directly under the application's main package (e.g., example.inventory and example.order). Types in these packages are considered part of the module's public interface.
    2. 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.

  4. Decouple modules using Application Events

    main

    To maintain high decoupling between application modules, use Spring's ApplicationEventPublisher instead 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()));
      }
    }
  5. How application modules work in Spring Modulith

    main

    An 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 @SpringBootApplication resides) 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 be public to 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 a package-info.java file).

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

  6. How Kafka event externalization works

    main

    The externalization process follows this lifecycle:

    1. Bootstrap: On application startup, the spring-modulith-events-kafka module registers an ApplicationModuleListener specifically for externalization.
    2. Event Publication: When a business method publishes an event (e.g., OrderCompleted) that is annotated with @Externalized, the infrastructure detects it.
    3. Registry Tracking: The Event Publication Registry creates a record to track the event's processing.
    4. Asynchronous Externalization: An asynchronous task (e.g., task-1) is triggered to handle the externalization via the registered ApplicationModuleListener.
    5. Kafka Dispatch: The listener invokes the Kafka operations to send the message.
    6. Completion: Once the message is successfully sent, the Event Publication Registry marks the publication as completed.
  7. How the Documenter abstraction works

    main

    The Documenter abstraction is used to generate documentation snippets from an ApplicationModules instance. 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.adoc file that links all existing diagrams and canvases together.

    To use it, you typically pass an ApplicationModules instance (obtained via ApplicationModules.of(Application.class)) to the Documenter constructor.

    ApplicationModules modules = ApplicationModules.of(Application.class);
    new Documenter(modules).writeModulesAsPlantUml();
  8. How Moments and TimeMachine work

    main

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

    • DayHasPassed
    • WeekHasPassed
    • MonthHasPassed
    • QuarterHasPassed
    • YearHasPassed

    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 extends Moments) instead. TimeMachine provides 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));
    }
  9. Configure Application-Module Aware Flyway Migrations

    main

    Spring 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-enabled configuration 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 named flyway_schema_history_$moduleIdentifier and 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.

  10. How reliable domain events work in Spring Modulith

    main

    Spring 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 the EventPublicationRegistry. These pending events can then be re-published upon application restart or via a scheduled process, ensuring eventual consistency.

  11. How the Event Publication Registry works

    main

    The 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 as COMPLETED upon successful execution. If the listener fails, the log entry remains, allowing for retry mechanisms.

    By default, all listeners annotated with @TransactionalEventListener are tracked. You can customize this via the spring.modulith.events.registry-trigger-annotation property.

  12. Configure application module test bootstrap modes

    main

    The @ApplicationModuleTest annotation allows you to control which modules are included in the Spring ApplicationContext during 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.