Spring Boot integration

Spring DDD is a set of Spring Boot auto-configurations. This page lists what it uses from Spring Boot, what the application provides, and what the application can replace.

DataSource and transactions

Spring DDD writes to the application’s primary DataSource through the JdbcTemplate and the NamedParameterJdbcTemplate Spring Boot configures for it. An application with more than one of either marks the one for that DataSource @Primary. The application’s primary PlatformTransactionManager must manage that DataSource; see Several DataSources.

On every engine, the events of one append are stored together or not at all, whatever the pool’s auto-commit setting. An append joins the transaction the event-sourcing repository opens for every save, and runs in a transaction of its own on the event store’s DataSource when there is none, for example when the application has no transaction manager. Without a transaction manager the events are therefore committed before the repository publishes them, so a failing event listener does not undo the save. An append called inside a transaction on another DataSource is not covered by that transaction. The framework rejects such an append with an IllegalStateException when the pool auto-commits; on a pool with auto-commit disabled it cannot tell, and the append is lost when the connection returns to the pool. With a transaction manager, the event-sourcing repository never appends that way.

Several DataSources

The event store, the capture topic, the consumers' checkpoints and the saga stores write to the application’s primary DataSource, through the JdbcTemplate and NamedParameterJdbcTemplate that Spring Boot configures for it. Command handlers, the event-sourcing repository and the saga engine open their transactions with the application’s primary PlatformTransactionManager. Both must belong together: a transaction on another DataSource does not cover the stores' writes. Those would then commit statement by statement, or be rolled back when the connection returns to a pool with auto-commit disabled, while the transaction on the other DataSource commits.

The framework therefore checks at startup that the primary transaction manager manages the stores' DataSource, and refuses to start otherwise. When an application with a second DataSource defines a transaction manager for it, mark the transaction manager for the primary DataSource as @Primary:

@Configuration
class DataSourceConfig {

    @Bean
    @Primary
    fun transactionManager(dataSource: DataSource) = DataSourceTransactionManager(dataSource) (1)

    @Bean
    fun reportingTransactionManager(@Qualifier("reportingDataSource") dataSource: DataSource) =
        DataSourceTransactionManager(dataSource)
}
1 Manages the primary DataSource, which holds the event store and the saga tables.

A second DataSource declared with @Bean(defaultCandidate = false) is not injected by type. Spring Boot’s DataSource, JdbcTemplate and transaction manager then stay on the primary one, and nothing else is needed. A JPA application’s JpaTransactionManager on the primary DataSource passes the check as well. A transaction manager bound to another kind of resource, such as a JMS or Kafka transaction manager, fails it: its transactions do not cover DataSource writes either.

Two cases are not checked. A transaction manager bound to no single resource, such as a JTA transaction manager, coordinates the DataSource itself. An application that replaces the event store, the capture topic’s storage, the consumers' checkpoint store or all of the saga stores with its own beans chooses their DataSource itself and is responsible for pairing it with the transaction manager.

A transaction the application runs past Spring, such as a container-managed JTA transaction without Spring’s JtaTransactionManager, is not visible to the framework, which then opens transactions of its own on the DataSource; run such transactions through a Spring transaction manager.

Database schema

Spring DDD creates the tables it needs at startup, on the application’s primary DataSource. Each area with tables of its own has one switch:

  • spring.ddd.eventsourcing.jdbc.initialize-schema

  • spring.ddd.messaging.jdbc.initialize-schema

  • spring.ddd.cqrs.jdbc.initialize-schema

  • spring.ddd.saga.jdbc.initialize-schema

Each takes one of:

always

Creates the tables at startup. The default.

embedded

Creates them only in an embedded database, such as H2 in tests, while production applies its own migrations.

never

Creates nothing. The application applies the DDL from Database table schemas.

The tables of JPA read models and JPA aggregates are the application’s own entities: Hibernate or the application’s migrations create them, never Spring DDD. Every switch is listed in Configuration Properties.

JSON

Spring DDD serializes with Jackson 3 mappers of its own, so the application’s Jackson modules and settings never change what it stores. Each can be replaced by declaring a JsonMapper bean of its name:

eventStoreJsonMapper

Events in the event store and aggregate snapshots.

captureTopicJsonMapper

Messages on the capture topic.

readModelJsonMapper

Read models that an in-memory projection copies, that a persistent projection publishes to its read-model topic, or that an ad-hoc call reads from one, and those of the JSON query store; see Read models are JSON.

sagaJsonMapper

Saga state; see Writing a saga.

Declare a replacement with @Bean("<name>", defaultCandidate = false). Without defaultCandidate = false it also replaces the application’s own JsonMapper, because Spring Boot backs off its mapper whenever another JsonMapper bean is a default candidate.

Clock

Spring DDD uses the application’s Clock bean when there is one, and Clock.systemUTC() otherwise; see Clock and time.

Threads and scheduling

spring.threads.virtual.enabled decides whether consumers apply on virtual threads or on a pool of platform threads. Sagas share the application’s TaskScheduler bean.

Shutdown

A stopping instance waits up to spring.ddd.messaging.consumer.graceful-shutdown-timeout, 20 seconds by default, for the messages its consumers are applying. Keep it below spring.lifecycle.timeout-per-shutdown-phase, 30 seconds by default, so that Spring Boot does not stop the consumers first.

Actuator and Micrometer

Both are optional. spring-ddd-starter-observability brings Spring Boot Actuator and adds endpoints, health indicators and metrics; see Observability.

Spring Data and JPA

Spring DDD finds aggregates, events, projections and sagas in the application’s auto-configuration packages: the package of the @SpringBootApplication class and below. Event-sourcing repositories are found there too; @EnableEventSourcingRepositories replaces that search with the packages it names. @EventSourcedScan replaces it for event-sourced aggregates and adds its packages to the search for events, commands and queries; see Scanning for aggregates. JPA aggregates and JPA read models are ordinary Spring Data JPA entities and repositories.

Spring Modulith