Interface CommandBus
- All Known Implementing Classes:
AbstractCommandBus,LocalCommandBus
public interface CommandBus
CommandBus / LocalCommandBus / DurableLocalCommandBus
The CommandBus pattern decouples the sending of commands from their handling
by introducing an indirection between the command and its corresponding CommandHandler.
This design provides location transparency, meaning that the sender does not need to
know which handler will process the command.
The LocalCommandBus is the default implementation of the CommandBus pattern.
Single CommandHandler Requirement
- Exactly One Handler: Every command must have one, and only one,
CommandHandler. - If no handler is found, a
NoCommandHandlerFoundExceptionis thrown. - If more than one handler is found, a
MultipleCommandHandlersFoundExceptionis thrown.
Command Processing and Return Values
- No Expected Return Value: According to CQRS principles, command handling typically does not return a value.
- Optional Return Value: The API allows a
CommandHandlerto return a value if necessary (for example, a server-generated ID).
Sending Commands
Commands can be dispatched in different ways depending on your needs:
1. Synchronous processing
- Method:
send(Object) - Usage: Use this when you need to process a command immediately and receive instant feedback on success or failure.
2. Asynchronous processing with Feedback
- Method:
sendAsync(Object) - Usage: Returns a
Monothat will eventually contain the result of the command processing. This is useful when you want asynchronous processing but still need to know the outcome.
3. Fire-and-Forget Asynchronous processing
- Method:
sendAndDontWait(Object)orsendAndDontWait(Object, java.time.Duration) - Usage: Sends a command without waiting for a result. This is true fire-and-forget asynchronous processing.
- Note: If durability is required (ensuring the command is reliably processed),
consider using
DurableLocalCommandBusfrom theessentials-components'sfoundationmodule instead.
Handling Command Processing Failures
Using send(Object) / sendAsync(Object)
- Immediate Error Feedback: These methods provide quick feedback if command processing fails (e.g., due to business validation errors), making error handling straightforward.
Using sendAndDontWait(Object)
- Delayed Asynchronous Processing: Commands sent via this method are processed in a true asynchronous fire-and-forget fashion, with an optional delay.
- Spring Boot Starters Default: When using the Essentials Spring Boot starters, the default
CommandBusimplementation isDurableLocalCommandBus, which placessendAndDontWait(Object)commands on aDurableQueuefor reliable processing. - Exception Handling: Because processing is asynchronous fire-and-forget (and because commands may be processed on a different node or after a JVM restart), exceptions can not be captured by the caller.
- Error Management: The
SendAndDontWaitErrorHandlertakes care of handling exceptions that occur during asynchronous command processing. - Retries: The
DurableLocalCommandBusis configured with aRedeliveryPolicyto automatically retry commands that fail.
Summary
- For Immediate Feedback:
Use
send(Object)orsendAsync(Object)to simplify error handling with instant feedback on command processing. - For True Asynchronous, Fire-and-Forget Processing:
Use
sendAndDontWait(Object)when you require asynchronous processing with retry capabilities. - Be aware that error handling is deferred to the
SendAndDontWaitErrorHandlerand managed by any configuredRedeliveryPolicy.
Example:
var commandBus = new LocalCommandBus();
commandBus.addCommandHandler(new CreateOrderCommandHandler(...));
commandBus.addCommandHandler(new ImburseOrderCommandHandler(...));
var optionalResult = commandBus.send(new CreateOrder(...));
// or
var monoWithOptionalResult = commandBus.sendAsync(new ImbuseOrder(...))
.block(Duration.ofMillis(1000));
In case you need to colocate multiple related command handling methods inside a single class then you should have your command handling class extend AnnotatedCommandHandler.
Example:
public class OrdersCommandHandler extends AnnotatedCommandHandler {
@Handler
private OrderId handle(CreateOrder cmd) {
...
}
@Handler
private void someMethod(ReimburseOrder cmd) {
...
}
}- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionaddCommandHandler(CommandHandler commandHandler) Add a new command handler.addInterceptor(CommandBusInterceptor interceptor) Add a new interceptordefault CommandBusaddInterceptors(List<CommandBusInterceptor> interceptors) Add new interceptorsFind aCommandHandlercapable of handling the given commandReturns an immutable list of interceptorsbooleanhasCommandHandler(CommandHandler commandHandler) Guard method to test if theCommandBusalready contains the commandHandlerbooleanhasInterceptor(CommandBusInterceptor interceptor) Guard method to test if theCommandBusalready contains the interceptorremoveCommandHandler(CommandHandler commandHandler) Remove a command handler.removeInterceptor(CommandBusInterceptor interceptor) Remove an interceptor<R,C> R send(C command) The send command synchronously and process the command on the sending thread<C> voidsendAndDontWait(C command) The send command asynchronously without waiting for the result of processing the Command.
The command handler cannot return any value when invoked using this method.<C> voidsendAndDontWait(C command, Duration delayMessageDelivery) The send command asynchronously without waiting for the result of processing the Command.
The command handler cannot return any value when invoked using this method.<R,C> reactor.core.publisher.Mono<R> sendAsync(C command) The send command asynchronously and process the command on aSchedulers.boundedElastic()thread
-
Method Details
-
getInterceptors
List<CommandBusInterceptor> getInterceptors()Returns an immutable list of interceptors- Returns:
- Returns an immutable list of interceptors
-
addInterceptor
Add a new interceptor- Parameters:
interceptor- the interceptor to add- Returns:
- this bus instance
-
addInterceptors
Add new interceptors- Parameters:
interceptors- the interceptors to add- Returns:
- this bus instance
-
hasInterceptor
Guard method to test if theCommandBusalready contains the interceptor- Parameters:
interceptor- the interceptor to check- Returns:
- true if the
CommandBusalready contains the interceptor, otherwise false
-
removeInterceptor
Remove an interceptor- Parameters:
interceptor- the interceptor to remove- Returns:
- this bus instance
-
addCommandHandler
Add a new command handler.- Parameters:
commandHandler- the command handler to add- Returns:
- this bus instance
-
removeCommandHandler
Remove a command handler.- Parameters:
commandHandler- the command handler to remove- Returns:
- this bus instance
-
send
<R,C> R send(C command) The send command synchronously and process the command on the sending thread- Type Parameters:
R- the return typeC- the command type- Parameters:
command- the command to send- Returns:
- the result of the command processing (if any)
- Throws:
MultipleCommandHandlersFoundException- If multipleCommandHandlerclaim that they can handle a given commandNoCommandHandlerFoundException- If noCommandHandler's can handle the given command
-
sendAsync
<R,C> reactor.core.publisher.Mono<R> sendAsync(C command) The send command asynchronously and process the command on aSchedulers.boundedElastic()thread- Type Parameters:
R- the return typeC- the command type- Parameters:
command- the command to send- Returns:
- a
Monothat will contain the result of the command processing (if any) - Throws:
MultipleCommandHandlersFoundException- If multipleCommandHandlerclaim that they can handle a given commandNoCommandHandlerFoundException- If noCommandHandler's can handle the given command
-
sendAndDontWait
<C> void sendAndDontWait(C command) The send command asynchronously without waiting for the result of processing the Command.
The command handler cannot return any value when invoked using this method.- Type Parameters:
C- the command type- Parameters:
command- the command to send- Throws:
MultipleCommandHandlersFoundException- If multipleCommandHandlerclaim that they can handle a given commandNoCommandHandlerFoundException- If noCommandHandler's can handle the given command
-
sendAndDontWait
The send command asynchronously without waiting for the result of processing the Command.
The command handler cannot return any value when invoked using this method.- Type Parameters:
C- the command type- Parameters:
command- the command to senddelayMessageDelivery- how long should we delay the command message sending- Throws:
MultipleCommandHandlersFoundException- If multipleCommandHandlerclaim that they can handle a given commandNoCommandHandlerFoundException- If noCommandHandler's can handle the given command
-
findCommandHandlerCapableOfHandling
Find aCommandHandlercapable of handling the given command- Parameters:
command- the command to handle- Returns:
- the single
CommandHandlerthat's capable of handling the given command - Throws:
MultipleCommandHandlersFoundException- If multipleCommandHandlerclaim that they can handle a given commandNoCommandHandlerFoundException- If noCommandHandler's can handle the given command
-
hasCommandHandler
Guard method to test if theCommandBusalready contains the commandHandler- Parameters:
commandHandler- the commandHandler to check- Returns:
- true if the
CommandBusalready contains the commandHandler, otherwise false
-