I am actually new in CQRS and Event Sourcing.
I got confused by Command Handler in Aggregate, when i saw this code :
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Aggregate
public class BankAccountAggregate {
@AggregateIdentifier
private UUID id;
private BigDecimal balance;
private String owner;
@CommandHandler
public BankAccountAggregate(CreateAccountCommand command){
AggregateLifecycle.apply(
new AccountCreatedEvent(
command.getAccountId(),
command.getInitialBalance(),
command.getOwner()
)
);
}
that command handler is just publishing an event. as you can see the naming of the object is AccountCreatedEvent
but it doesn't mean the Account is created
right?
the account creation is in EventHandler that subscribe AccountCreatedEvent
:
@Slf4j
@RequiredArgsConstructor
@Component
public class BankAccountProjection {
private final BankAccountRepository repository;
private final QueryUpdateEmitter updateEmitter;
@EventHandler
public void on(AccountCreatedEvent event) throws Exception {
log.debug("Handling a Bank Account creation command {}", event.getId());
BankAccount bankAccount = new BankAccount(
event.getId(),
event.getOwner(),
event.getInitialBalance()
);
this.repository.save(bankAccount);
Boolean isActive = AggregateLifecycle.isLive();
}
}
What i know is :
So why not we put the logic of Account creation
in the Command Handler? what is the purpose of doing this?
full source code at this link.
You are mixing some concepts. Let's clarify them.
If you implement event sourcing, it means that your source of truth is the events themselves. You store events, not a concrete "state" (an entity). Let's see some pseudo-code:
To create a new account:
function createAccount(data) {
event = new AccountCreatedEvent(data)
eventStore.save(event)
}
To withdraw from an account, for instance:
function withdraw(data) {
events = eventStore.getEvents(data.accountId)
account = new Account()
account.apply(events)
account.withdraw(data)
newEvents = account.newEvents
eventStore.save(newEvents)
}
(This methods would be called by your command handler)
As you see, you generate account
from its events, instead of read it from a repository. Account class would be something like this:
class Account {
amount = 0
newEvents = []
function apply(events) {
events.forEach(event => {
if event == AccountCreatedEvent {
this.amount = event.initialAmount
} else if (event == WithdrawalApplied) {
this.amount = this.amount - event.amount
} // more event types
})
}
function withdraw(data) {
// here is where you ensure aggregate invariants (business rules)
if this.amount == 0 {
throw Error("no money")
}
this.amount = this.amount - data.amount
newEvents.add(new WithdrawalApplied(data))
}
}
So to your question:
that command handler is just publishing an event. as you can see the naming of the object is AccountCreatedEvent but it doesn't mean the Account is created right?
The answer is that you should store the event in command handler. And that precisely means the account is created. Then, you can publish the event as your are doing, if needed. Keep reading.
With CQRS you just separate your queries and commands, but this technique can be applied without event sourcing at all.
Since your source of truth consists of a bunch of events, when client wants to query an account by ID, for instance, you need to query all the events and build your account from them. This can be slow. So, when using CQRS and event sourcing, to achieve faster reads, you can apply this technique.
Basically, it consists of listen to the events and build a pre-built projection of the aggregate. This projection can be stored in a MongoDB, PostgreSQL or even a file. That's an implementation detail.
The image below illustrates how three techniques (CQRS, Event Sourcing and Projections) work together.