Command Processing
This page provides specifics around tuning the command processing within an Axon application.
Command sequencing
Command sequencing ensures that commands that aim to modify the same entities are processed sequentially, preventing optimistic locking in high-throughput applications. This feature is enabled by default in Axon Framework and operates transparently without requiring changes to existing command handlers.
Why command sequencing?
In high-throughput applications, multiple commands may target the same entities concurrently. Without sequencing, concurrent state modifications on the entities will lead to optimistic locking failures with a high probability. When two commands attempt to update the same entities simultaneously, subsequent commands will fail to commit their modifications, requiring custom retry logic to be implemented.
Command sequencing solves this problem by ensuring that commands targeting the same entities execute sequentially while maintaining full concurrency for commands targeting different entities. This eliminates conflicts without sacrificing overall system throughput.
Default behavior
Command sequencing is enabled by default in Axon Framework. The framework automatically configures a CommandSequencingInterceptor with the RoutingKeySequencingPolicy, which sequences commands based on their routing key.
The default behavior provides an optimal balance between consistency and performance:
-
Commands with the same routing key (typically part of the targeted entity identifier) execute sequentially - one must complete before the next begins
-
Commands with different routing keys can execute concurrently - no waiting is required
-
Commands with no routing key or an empty routing key are not sequenced and can execute concurrently
This means your application benefits from command sequencing immediately upon using Axon Framework, with no configuration required. The feature operates transparently at the handler interceptor level, after command routing has occurred.
How it works
Command sequencing is implemented through the CommandSequencingInterceptor, a handler interceptor that manages execution order:
-
Sequence identification: When a command arrives, the interceptor consults the configured
SequencingPolicyto extract a sequence identifier -
Lock management: The interceptor maintains a lock for each unique sequence identifier
-
Sequential execution: Commands with the same sequence identifier wait for the previous command to complete before proceeding
Because sequencing happens at the handler interceptor level (after routing), it has no impact on command dispatch or routing logic. The sequencing is completely transparent to your command handlers - no code changes are required.
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 the following implementations:
-
RoutingKeySequencingPolicy(default): Sequences commands by their routing key, which typically corresponds to the entity identifier. Commands with null or empty routing keys are not sequenced and thus can execute concurrently. This is the default policy and works well for most applications. -
NoOpSequencingPolicy: Disables all command sequencing. Use this policy when you want to opt-out from command sequencing.
You can implement the SequencingPolicy interface to create custom sequencing logic based on command metadata, payload properties, or any other criteria to set up your own sequencing logic.
Configuration
Custom sequencing policy
You can implement custom sequencing logic by implementing the SequencingPolicy interface. For example, to sequence commands by tenant ID from metadata:
public class TenantBasedSequencingPolicy implements SequencingPolicy {
@Override
public Optional<Object> sequenceIdentifierFor(CommandMessage command,
ProcessingContext context) {
// Sequence commands by tenant ID from metadata
return Optional.ofNullable(command.metadata().get("tenantId"));
}
}
Register your custom policy:
-
Axon Configuration API
-
Spring Boot auto configuration
public void registerCommandSequencingPolicy(MessagingConfigurer configurer) {
configurer.componentRegistry(cr -> cr.registerComponent(SequencingPolicy.class,
MessagingConfigurationDefaults.COMMAND_SEQUENCING_POLICY,
c -> new TenantBasedSequencingPolicy()));
}
@Configuration
public class AxonConfig {
@Bean
public SequencingPolicy<? super CommandMessage> commandSequencingPolicy() {
return new TenantBasedSequencingPolicy();
}
}
You can also implement selective sequencing. For example, to skip certain command types:
public class SelectiveSequencingPolicy implements SequencingPolicy<CommandMessage> {
private final SequencingPolicy<? super CommandMessage> defaultPolicy;
private final List<String> skipTypes;
public SelectiveSequencingPolicy(SequencingPolicy<? super CommandMessage> defaultPolicy, List<String> skipTypes) {
this.defaultPolicy = defaultPolicy;
this.skipTypes = skipTypes;
}
@Override
public Optional<Object> sequenceIdentifierFor(CommandMessage command,
ProcessingContext context) {
// Skip sequencing for read-only commands
if (skipTypes.contains(command.payloadType().getSimpleName())) {
return Optional.empty();
}
// Use default policy for other commands
return defaultPolicy.sequenceIdentifierFor(command, context);
}
}
Disabling command sequencing
If you need to disable command sequencing entirely, register a NoOpSequencingPolicy:
-
Axon Configuration API
-
Spring Boot auto configuration
public void registerCommandSequencingPolicy(MessagingConfigurer configurer) {
configurer.componentRegistry(cr -> cr.registerComponent(SequencingPolicy.class,
MessagingConfigurationDefaults.COMMAND_SEQUENCING_POLICY,
c -> NoOpCommandSequencingPolicy.INSTANCE));
}
@Configuration
public class AxonConfig {
@Bean
public SequencingPolicy<? super CommandMessage> commandSequencingPolicy() {
return NoOpSequencingPolicy.INSTANCE;
}
}