Command handling
Command handler
Command handlers are components that react on incoming commands. A command handler's responsibility is to validate a command against the current state of an aggregate. If the command is valid, we return one or more events.
You can create a command handler by simply annotating your command handling methods with @HandleCommand
:
pblic class CustomerCommandHandler {
@HandleCommand
public CustomerEvent handle(Customer currentState, CreateCustomer command) {
if (currentState != null) {
throw new ValidationException("Customer already exists");
}
return CustomerCreated.builder()
.customerId(command.getCustomerId())
.firstName(command.getFirstName())
.lastName(command.getLastName())
.build();
}
@HandleCommand
public CustomerEvent handle(Customer currentState, ChangeFirstName command) {
if (currentState == null) {
throw new ValidationException("Customer does not exist");
}
return FirstNameChanged.builder()
.customerId(command.getCustomerId())
.firstName(command.getFirstName())
.build();
}
}
The first parameter of the command handling method is the current state of the aggregate. The second parameter is the command that is being handeled. We return one or more events if the command is valid. If the command is invalid, we can reject it by by throwing an exception. EasySourcing also has built-in support for Bean Validation (JSR 380).
Only one command handler may exist for a given command. Any other command handlers for the same command will not get executed.
Best practises for command handlers:
- Command handlers should be pure functions and should not block execution.
- Command handlers should only perform validation checks and return 0..n events.
- Don't call external systems to validate your aggregate. If you need to, then consider if you got your aggregate boundaries correct.
- Don't put logic like sending out emails or updating view models in command handlers. These kind of logic should rather be executed in event handlers.
Eventsourcing handler
An eventsourcing handler applies events to the current state of the aggregate and returns a new updated state. See below for an example:
public class CustomerEventSourcingHandler {
@ApplyEvent
public Customer apply(Customer currentState, CustomerCreated event) {
return Customer.builder()
.id(event.getCustomerId())
.firstName(event.getFirstName())
.lastName(event.getLastName())
.build();
}
@ApplyEvent
public Customer apply(Customer currentState, FirstNameChanged event) {
return currentState.toBuilder()
.firstName(event.getFirstName())
.build();
}
}
We annotate our methods with @ApplyEvent
. These methods takes two parameters, the first one is the current state of the aggregate and the second parameter is the event to apply. The return value of an the method should always be the type of the aggregate. In this case, we return a new copy of the customer with updated state. Although it is not required to return a new copy, it is recommended that you use immutable objects as much as possible.
Best practises for eventsourcing handlers:
- Eventsourcing handlers should be pure functions and should not block execution.
- Eventsourcing handlers should not have any side effects and they should not modify the passed aggregate state. They should rather do a deep copy of the passed aggregate, apply the event and return this altered aggregate.
Dispatching commands
Sending commands is as easy as:
public class SomeService {
private CommandGateway commandGateway;
public void sendCommand() {
CreateCustomer command = CreateCustomer.builder()
.customerId("cust-123")
.firstName("John")
.lastName("Doe")
.build();
commandGateway.send(command);
}
}
Spring Boot
When using Spring Boot, you can just autowire an instance of CommandGateway
.