Skip to content
Exposed docs1.12

DDD Spring Modulith Demo

Latest stable Based on Exposed release 1.12.1

This runnable Spring Boot example keeps order acceptance, event publication, and shipping reservation in named application modules. It demonstrates a DDD-style boundary using Exposed persistence without making the shared contracts depend on Spring or JaVers types.

The orders module accepts an order and publishes OrderAcceptedEvent through its named events interface. The shipping module consumes that public event with a stable listener and persists a reservation. modulithinvalid deliberately contains an invalid dependency so Spring Modulith verification has a negative case to detect.

The concrete tables are DDD_MODULITH_ORDERS, EVENT_PUBLICATION, and DDD_MODULITH_SHIPPING_RESERVATIONS.

Terminal window
./gradlew :examples-ddd-spring-modulith-demo:test --no-configuration-cache --no-daemon --console=plain

The tests verify module structure as well as the order-to-shipping flow. Read the invalid package alongside the valid module interfaces to see which dependency direction the verifier protects.

  • Publish stable event DTOs from an explicitly named interface package.
  • Consume events through @ApplicationModuleListener with a stable listener id.
  • Make replay-sensitive writes idempotent by a business key such as orderId; shipping checks whether a reservation already exists.
  • Keep aggregate and event contracts Spring-neutral. Spring Modulith supplies lifecycle integration in this example, not the domain type system.
  • Keep JDBC persistence inside the owning transaction boundary; do not let another module reach into an internal repository.

Start with orders, follow OrderAcceptedEvent into shipping, then inspect ShippingReservationRepository and its schema initializer. Finally run the structure tests and study modulithinvalid as the intentionally rejected arrangement.

The example shows how to keep domain events and module boundaries explicit while persistence and application orchestration evolve independently.

Use it as a small Spring Modulith verification fixture or as a reference for an order-to-shipping event flow. It is not a production deployment template.

This is an example application and does not publish a library artifact. Run its Gradle test task from the repository root.

Named interfaces expose stable events; internal repositories and invalid module dependencies remain private to their owning module.

Run ./gradlew :examples-ddd-spring-modulith-demo:test --no-configuration-cache --no-daemon --console=plain and inspect the structure and flow test reports.

  • Publish OrderAcceptedEvent through the named events interface.
  • Consume it with @ApplicationModuleListener.
  • Persist the shipping reservation through its internal repository.

Keep event DTOs stable, use business-key idempotency for replay-sensitive writes, and keep aggregate contracts independent of framework types.

Spring Modulith supplies module verification and event lifecycle integration; Exposed owns JDBC persistence inside the application transaction boundary.

Configure the test database and Spring application context through the example’s Gradle and test-resource settings. Keep production credentials outside this fixture.

The structure test must reject modulithinvalid. Duplicate event delivery and persistence conflicts should be handled by the business key and transaction boundary.

Observe event publication, listener completion, reservation writes, and verification failures separately. Record correlation identifiers when adapting the flow to a service.

Run the Gradle test task and read both module-structure failures and order-to-shipping flow assertions.

Read orders, follow OrderAcceptedEvent into shipping, then compare the valid modules with modulithinvalid.

The example does not define a production messaging topology, retry policy, deployment model, or cross-service consistency guarantee.