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?