Cross-cutting concerns
Behaviour such as tenant scoping, the acting user, audit trails, distributed tracing, and authorisation applies across many commands, queries, events, and sagas — but it is not domain logic. Embedding it in every handler signature couples the model to infrastructure concerns. Spring DDD keeps it separate with two complementary mechanisms that compose cleanly without touching handler or aggregate code.
The two mechanisms
- Message metadata
-
A
MessageMetadataenvelope of key-value pairs that travels with each dispatched message and with the domain events it produces. Providers attach context on the request thread; handlers declare parameters annotated with@MetadataValueto receive it.MessageMetadatais framework-owned and distinct from Spring Messaging’sMessageHeaders. - Message interceptors
-
Beans that wrap handler dispatch through an explicit chain, placing cross-cutting behaviour in one location rather than every handler. Interceptors are a Spring DDD bus/dispatch feature — not Spring AOP — and run outside the handler transaction.
The following diagram shows how both mechanisms relate to a single handler invocation:
Which mechanism to use
Use message metadata when a handler — or a downstream event handler — needs to read contextual data (tenant, actor, correlation id).
Use message interceptors when you need behaviour around dispatch: audit logging, authorisation vetoes, or timing measurements.
They compose naturally: an interceptor commonly reads metadata via CurrentMessageMetadata.get() to make a decision without requiring any change to the handler it wraps.
Routing
A related but distinct cross-cutting concern is where a message goes. Routing can send a command, query, or domain event to an external system — chosen by message type, without changing any call site — while everything with no route continues to dispatch to the local bus. Like metadata and interceptors, its core is message-type-agnostic and lives in the messaging layer; one decision layer covers commands, queries, and events, differing only in how the chosen destination is used.
Related pages
-
Message metadata — attaching and reading per-message context with
@MessageMetadataProviderand@MetadataValue. -
Message interceptors — wrapping dispatch with
@MessageHandlerInterceptorandInterceptorChain. -
Routing — sending a command, query, or domain event to an external system by message type instead of (or, for events, alongside) a local handler.
-
Clock and time — how the framework resolves its
Clockand how to make time deterministic. -
Serialization customization is documented per-store today; for saga state serialization see Writing a saga.