Part of the Aggregate + Event Sourcing (A+ES) methodology feels uncomfortable. Why does it feel uncomfortable? (As I've read from other questions and the book itself, it's a new-ish way of thinking, and the literature may not be great.)
I think part of it is the separation between state ownership, and state transition source.
The Domain Model has an Aggregate. Clients can affect changes in the Aggregate by Application Services. Application Services are the direct client of the Domain Model. Application Services call methods on the Aggregate. The Aggregate can publish Domain Events. The Aggregate decides if a Domain Event gets published, and if so, which Domain Event gets published. Typically, the Event reflects some change to the state of the Aggregate; or, the Event reflects something that has happened. Some change has occurred, and an Event followed that occurence. The change happened-before the Event. The Event happened-after the change.
With A+ES, we reconstitute the state of an Aggregate from the Events that have occurred. When a new Aggregate instance is created, there are no Events to reconstitute the state from, so the Aggregate is a blank slate. When the Aggregate is commanded to do something by an Application Service, some change happens, and an Event is published that records that change. Later, when we want to use that Aggregate instance again, we retrieve the Event Stream for that Aggregate instance, and feed the Events into it.
This I think is the part that makes me feel uncomfortable. The Aggregate takes commands from Application Services, performs some action that results in a state change, and publishes an Event that a change has occurred. With A+ES, the Aggregate must also take Events that it had published previously, and handle them accordingly. It has to react to the commands that generate Events, and now also has to react to the Events that it generated previously.
The example code in IDDD (Implementing Domain Driven Design) encapsulates a separate State object for the
Aggregate. The State object is then the one that is responsible for
mutating the state based on Events. In this example, the Aggregate
receives the Events by means of its constructor, but then completely
hands off the Events to a State object created in the constructor. This
is called out as separating the behavior and the state into two
different classes, with the behavior class (the Aggregate) referencing
the state class to make decisions, and only permitting the state class
to be modified via Events. The state can be queried directly from the
Aggregate (eg. state.Name
or state.Id
), but it cannot be changed
directly by the Aggregate (there are no setter methods available to
support state.Name = "...";
or state.SetName(...);
). The only way to
change anything about the State, is to give the State an Event, and let
the State determine what happens with that Event. Here, it seems
that the Event comes before the change. Instead of a state change
triggering an Event, the Event triggers the state change. The command
generates an Event, and later, the Event becomes a command. To me, it
feels like an unfamiliar temporal decoupling, and an inversion of
control. It almost feels like some sort of inverted invariant
enforcement. Instead of the Aggregate making some change to the
state, ensuring the invariant is enforced, and publishing an Event that
the change was made (and implicitly, that the change satisfied the
invariant), the Aggregate instead tells its state "perform whatever
actions necessary to make this true/enforce this invariant".
A (maybe not great) example might be the difference between
void LockAccount() {
AccountLocked = true;// invariant enforced here
var event = AccountLocked(...);
PublishEvent(event);
}
and
void LockAccount() {
var event = AccountLocked(...);
state.Mutate(event);// "perform whatever actions necessary for the
// 'AccountLocked' invariant to be enforced."
PublishEvent(event);
}
Is there some fundamental concept that I've missed that is making this feel uncomfortable, or is my understanding correct, and the uncomfortable feeling I get is simply unfamiliarity with this design paradigm?
EDIT: To explain from the comments, nothing is inherently wrong about the second code example. It makes sense mechanically. Part of the uncomfortable feeling comes from the sequence of events. If we only had the Aggregate class, and it managed behavior and state, then it would be weird if we published an Event before the appropriate state change had occurred. I would expect the state to be changed (flags set, values changed, objects replaced, etc) before an Event reflecting that state change was created.
With A+ES, the Event is created before the necessary state changes (flags set, values changed, objects replaced, etc) have occurred in the object. In fact, the Event is entirely what drives the state changes.
I think I may have found a way to reconcile this though. It seems like Events immediately follow "decision points". A decision point seems like a point in time, or code, that something has been undeniably decided.
Let's say we had an Aggregate, that had one method, and calling that method could result in two different Events.
void DoSomething() {
if (state.SomethingIsTrue) {
var trueEvent = TrueEvent(...);
state.Mutate(trueEvent);
}
else {
var falseEvent = FalseEvent(...);
state.Mutate(falseEvent);
}
}
In this code block, we have two decision points, nicely corresponding to the if and else blocks. If something is true, we have decided what we're going to do next. If something is false, we also have decided what we're going to do next. In both cases, an Event is published after the decision point has been reached.
If we apply this to the LockAccount examples above
void LockAccount() {
// decision to lock the account happened before now
AccountLocked = true;// state is changed to reflect the decision
var event = AccountLocked(...);// event reflects that a decision has been made
...
}
void LockAccount() {
// decision to lock the account happened before now
var event = AccountLocked(...);// event reflects that a decision has been made
state.Mutate(event);// state is changed to reflect the decision
...
}
Looking at it this way, it's not the Event that drives the change, nor is it the Aggregate that drives the change, but it's the decision that drives the change. Regardless of which comes first, changing state and sending event, or sending event to change state, both happen only after a specific decision has been made. The Event then doesn't reflect that the state has been changed, but that a decision has been made.