Reputation: 1765
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.
Upvotes: 2
Views: 1668
Reputation: 57279
I am actually new in CQRS and Event Sourcing. I got confused by Command Handler in Aggregate, when i saw this code
Not your fault - I've been studying CQRS/ES for quite some time now, and I find Nebrass Lamouchi's design to be alien.
In reading this essay, I see a number of other red flags; so I'd encourage you to look to other sources if you want to learn the "best practices" associated with these design ideas.
Is Command Handler is just receiving commands from the bus and publish the event?
Almost; in general, when we talk about the command handler we are usually describing a piece of application code that consumes messages off of the queue, and in doing so updates our persistent data store.
So if you were to write out the code long hand, the usual pattern would look like
void handle(Command c) {
root = repo.get(c.accountId())
root.updateMyself(c)
repo.save(root)
}
If you also wanted to broadcast events at this point, then that too would normally happen here in the application layer
void handle(Command c) {
root = repo.get(c.accountId())
root.updateMyself(c)
repo.save(root)
// In "event sourced" designs, this sort of publish is
// normally "outside" of the transaction. When using
// a relational data store, it might not be. Tradeoffs
publish(root.events())
}
But we don't see that here because Lamouchi is also demonstrating the Axon framework, which reduces the amount of boilerplate code - in effect hiding the plumbing.
Is that a good idea? Take some time to watch 8 Lines of Code, by Greg Young.
Upvotes: 2
Reputation: 26084
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.
Upvotes: 4