Command Processing
Command processing is usually tuned to keep writes for the same entity in order while preserving concurrency for unrelated commands. The default behavior in Axon Framework 5 already handles the common case, so most applications only need to customize sequencing when the default routing key is not the right boundary.
Command sequencing
Command sequencing ensures that commands targeting the same entity are handled sequentially. This prevents optimistic locking conflicts when several commands arrive for the same entity at the same time.
When sequencing matters
Use sequencing when:
-
several commands can target the same entity concurrently
-
the entity changes are not safe to apply in parallel
-
you want the framework to serialize conflicts before they reach the repository
How it works
Command sequencing is implemented by the CommandSequencingInterceptor.
It is a handler interceptor, so it runs after routing and before the command handler executes.
-
The interceptor asks the configured
SequencingPolicyfor a sequence identifier. -
Commands with the same identifier are processed sequentially relative to each other.
-
Commands with different identifiers can still execute concurrently.
-
Commands for which the policy returns
Optional.empty()bypass sequencing entirely.
The policy is applied at command handling time, so it does not change dispatching or routing behavior. That makes it a good fit for fine-grained control over concurrency without changing handlers.
The SequencingPolicy interface
The SequencingPolicy interface determines which commands should be sequenced together:
public interface SequencingPolicy {
Optional<Object> sequenceIdentifierFor(CommandMessage command, ProcessingContext context);
}
Axon Framework provides a few useful policies:
-
RoutingKeySequencingPolicy(default) sequences commands by routing key. This is the default policy and matches the common entity identifier use case. -
NoOpSequencingPolicydisables sequencing entirely and always returns an empty sequence identifier.
You can also implement a custom policy when the routing key is not the right boundary. Common alternatives are metadata-driven sequencing, tenant-based sequencing, or payload-property sequencing.
Configuration
Custom sequencing policy
The following policy sequences commands by tenant identifier in metadata:
public class TenantBasedSequencingPolicy implements SequencingPolicy {
@Override
public Optional<Object> sequenceIdentifierFor(CommandMessage command, ProcessingContext context) {
return Optional.ofNullable(command.metadata().get("tenantId"));
}
}
Register the policy as the command sequencing policy:
-
Axon Configuration API
-
Spring Boot auto configuration
public void configureSequencingPolicy(MessagingConfigurer configurer) {
configurer.componentRegistry(cr -> cr.registerComponent(
SequencingPolicy.class,
c -> new TenantBasedSequencingPolicy())
);
}
@Configuration
class MessagingConfiguration {
@Bean
public SequencingPolicy<? super CommandMessage> commandSequencingPolicy() {
return new TenantBasedSequencingPolicy();
}
}
If a command does not need sequencing, return Optional.empty().
That lets the framework fall back to concurrent execution for those commands.
Disabling sequencing
If command handling is already serialized by another mechanism, register NoOpSequencingPolicy.INSTANCE.
public void configureSequencingPolicy(MessagingConfigurer configurer) {
configurer.componentRegistry(cr -> cr.registerComponent(
SequencingPolicy.class,
c -> NoOpSequencingPolicy.INSTANCE)
);
}