Saturday, January 13, 2018

Event Sourced Aggregates Part 6: Smarter Events

In this 6th and final post in my series about event sourced aggregates, I will make the events a little bit smarter. So far events have been completely stupid object that just carry some data around, but has no behavior. In this post I give every event a little bit of behavior by moving the 'When' methods from the aggregate to the events. The result is that the aggregate becomes anemic (that is, no behavior, just data), does not violate Open/Closed principle anymore, and that the events become more self contained.

In the 5th post I made an attempt at moving the 'When' methods out of the aggregate to get to a design where the aggregate does not violate Open/Closed. I did so by introducing a new abstraction - an aggregate projector - but that just ended up with the same Open/Closed violation that the aggregate suffered from originally. Therefore I take another approach in this post.

Smarter events

Let's, once again, see how the aggregate looks after domain logic was moved from the aggregate to the command handlers in post 4:

And the 'UsernameChangedEvent' looks like this:

In order to keep the aggregate from growing and growing as features are added to the system, I will move the 'When(UsernameChangedEvent e)` to a `When` method on the event itself, like this:

Now the event holds the data of the event and is responsible for applying the state changes to the aggregate that the event implies. That is: A domain event - like the UsernameChangedEvent - is an indication that something happened to the aggregate, i.e. there was a state change to the aggregate. The 'When' method on the event applies that state change to aggregate that it gets passed in as an argument.
When moving the 'When' method from the aggregate to the event the signature of the method must change a bit to 'void When(UserAggregate aggregate)'. Notice that this signature is not specific to the 'UsernameChangedEvent', but will be the same for all events. That turns out to be a quite handy side effect of moving the `When` methods. More on that in the next section. Since the `When` signature is the same for all events I'll go ahead and add it to the event interface:

Before the 'Event' interface was just a marker interface. Now it shows that all events have a 'When' method.

Event replay revisited

Moving the 'When' methods impact how event replay is done. Remember from the first post that what is stored in the database are the events. When we need an aggregate all the events emitted on that aggregate are read from the database, and then the 'When' method for each one is called. Since the 'When' methods each apply the state changes to aggregate implied by the event the end result is an aggregate object in the correct current state of that aggregate. The replay process goes like this:

Where the event replay is done by the 'Replay' method on the abstract class 'Aggregate'. The special sauce in this is in the implementation of the 'Play' method which - as shown in the first post of the series - involves using reflection to find a suitable 'When' method. This becomes a lot simpler now that all events implement an interface with a 'When' method on it:

This simplification was not the goal of the refactoring done in this post, but a nice side effect, and as such an indication that this is indeed a good road to follow.

The anemic aggregate

With the `When` methods moved to the events the aggregate has become anemic. And that is a good thing in this case. The UserAggregate is reduced to this:

which is simply a representation of the current state of a user. That is the essence of what the 'UserAggregate' is. Moreover this only changes if we have reason to change how the current state of a user looks, which I consider a good reason to change that class. Moreover the 'UserAggregate' no longer violates the Open/Closed principle since new domain behavior can be added by adding new commands, command handlers and events, but without changing the 'UserAggregate'.
Often an anemics domain model is seen as an anti pattern in objec oriented code. I agree. But only when looking at the domain model as a whole, not when looking at just one class - like the 'UserAggregate'. My point here is, that looking a the domain model as a whole includes commands, command handlers and events. In that perspective the domain model is not anemic - only the user aggregate is anemic.

Wrapping up

In the first post of this series I outlined what I see as a typical C# implementation of event sourced aggregates. I also argued that, that implementation leads to the aggregates violating the Open/Closed principle. In the third and fourth posts I solved half of the problem by making the command handlers smarter, and in this post I solved the second half of the problem by making events smarter.

The final code is on Github, as are the intermediate steps (see other branches on that same repository).