Computing values in command handler vs. event handler

180 views
Skip to first unread message

Steven Grimm

unread,
Dec 16, 2016, 1:03:05 AM12/16/16
to DDD/CQRS
I've seen code like this (pseudocode):

void handle(WithdrawMoneyCommand command) {
  publishEvent
(new MoneyWithdrawnEvent(accountId, command.amount, balance - command.amount));
}

void on(MoneyWithdrawnEvent event) {
  balance
= event.getNewBalance();
}

Here, the final balance is computed in the command handler, included as an event field, and copied to the aggregate in the event handler. But that seems wrong on a conceptual level: the business event that happened wasn't, "Joe withdrew $50 to reach a balance of $1234," but rather, "Joe withdrew $50." The fact that the balance is now $1234 is a *result* of the thing that happened, not the actual thing; the balance is just a computation over the event stream.

The other approach is

void handle(WithdrawMoneyCommand command) {
  publishEvent
(new MoneyWithdrawnEvent(accountId, command.amount));
}

void on(MoneyWithdrawnEvent event) {
  balance
= balance - event.amount;
}

Here the balance is adjusted in the event handler as a result of the event, and the event describes the action that was taken rather than what effect it had on the aggregate's values.

Aside from arguably being semantically closer to representing the business events, the second approach means that if there's a bug in the computation, I can fix the bug and replay the event log, resulting in the correct aggregate state. But that's a double-edged sword; once a fix is in place, I'm unable to recreate the current (incorrect) state of the world by replaying events.

Am I missing other aspects of this, or is "what happens when you change the logic and replay" the main consideration? What do people take into account when choosing one of these approaches over the other?

Homan Chou

unread,
Dec 19, 2016, 12:19:13 AM12/19/16
to DDD/CQRS
I like the 2nd approach better because the command doesn't have to have an exclusive lock on the balance in order to create the event.  Perhaps another withdrawal is happening at the same time, or slightly before it, in which case the computation of the balance in this event would end up being wrong if it was beat out by another command?

However, I think there are merits to both approaches, like so many things in DDD, it depends your your business.  When I used to balance my checkbook, I would write the debit / credit amount and fill in the running balance too.  So cementing the running balance into the event is a good way to preserve the history of what we thought was true at the time.  That's good for knowing "how we got to this balance".  And if there is a bug that you fix, you can keep those events as the same and create a new compensating event to make it right going forward.

Mischa Kölliker

unread,
Dec 21, 2016, 5:02:09 AM12/21/16
to DDD/CQRS

I'm just following this rule:
Business relevant facts (including all default/initial status) must go to the event
All aggregation based on these facts can happen in the event handler
Additionally, the current computed aggregation can go to the event, but just for informational purposes

If you later adjust the aggregation computation logic, you do not fake history, or historical state, because this aggregated values are not facts. The basic data is fact, everything else is just a conclusion drawn from these facts.
If you issue a bill based on such computed values, then this will be another event containing the billed value, what is again a fact (that probably needs to be compensated somehow later if there was a computation error)
Reply all
Reply to author
Forward
0 new messages