Reputation: 42125
This question is about DDD and Event Sourcing where entities within an Aggregate other than the Aggregate Root have event-generating behaviour.
What follows is an example of the situation I describe, where I'm sure I want to encapsulate some logic inside other entities within the Aggregate. This may involve suspension of disbelief with respect to the actual example and whether it is a good model or not. :)
I'm trying to model a DeliveryRun
Aggregate Root (AR), which is the trip a vehicle makes to perform a delivery. Before it departs, it must have an up to date DeliveryManifest
. The "up-to-dateness" of it suggests to me that the DeliveryManifest
be an entity within the DeliveryRun
consistency boundary defined by the AR.
Okay so far.
I'm using an Event Sourcing approach for this - the approach as taught by Greg Young and implemented in the Regalo library. This means the AR (DeliveryRun
) need not actually have any entities if there is no behaviour for them (e.g. a SalesOrder
may not have SalesOrderLines
, because it records events such as ItemsAdded
/ItemsRemoved
instead).
However, there is to be some logic around the DeliveryManifest
. Specifically, once the manifest has first been requested, when items are added to the delivery, a new version of the manifest needs to be created. This means we can ensure drivers don't depart without the most up-to-date manifest available.
If I were to encapsulate the logic inside the DeliveryManifest
object (which won't be serialised and stored; we're using Event Sourcing and it's not the AR), how do I capture events?
Should the events be generated by the DeliveryManifest
entity, but saved against the DeliveryRun
itself (which would then need to know how to replay those events into the DeliveryManifest
when loaded from the event store)?
Should there be no DeliveryManifest
(except perhaps as a data structure) and all the logic/events be implemented directly by the DeliveryRun
?
Should the DeliveryManifest
be it' own AR and make sure the DeliveryRun
is told of the current manifest's ID? Since that takes the manifest object outside the consistency boundary of the DeliveryRun
, I would need to build some event handling to subscribe to changes in the DeliveryRun
that are relevant to the manifest so it can be updated/invalidated etc accordingly.
Implement a different style for capturing the events similar to Udi's DomainEvents pattern. This means changing the Regalo library, though I think it could be made to support both patterns fairly easily. This would allow all events generated by all entities within the aggregate to be captured so they can be saved against the AR. I'd need to think of a solution for loading/replaying though...
Upvotes: 2
Views: 1061
Reputation: 2990
The mechanical answer ... where you can dream up lots of variations. Basically, you'll have to decide who is going to collect all those events: either the root (shown here), or each entity (approach not shown here) separately. Technically you have lots of options to implement the observation behavior (think Rx, hand-coded mediator etc) shown below. I surfaced most of the infrastructure code into the entities (missing abstractions here).
public class DeliveryRun {
Dictionary<Type, Action<object>> _handlers = new Dictionary<Type, Action<object>>();
List<object> _events = new List<object>();
DeliveryManifest _manifest;
public DeliverRun() {
Register<DeliveryManifestAssigned>(When);
Register<DeliveryManifestChanged>(When);
}
public void AssignManifest(...) {
Apply(new DeliveryManifestAssigned(...));
}
public void ChangeManifest(...) {
_manifest.Change(...);
}
public void Initialize(IEnumerable<object> events) {
foreach(var @event in events) Play(@event);
}
internal void NotifyOf(object @event) {
Apply(@event);
}
void Register<T>(Action<T> handler) {
_handlers.Add(typeof(T), @event => handler((T)@event));
}
void Apply(object @event) {
Play(@event);
Record(@event);
}
void Play(object @event) {
Action<object> handler;
if(_handlers.TryGet(@event.GetType(), out handler)) {
handler(@event); //dispatches to those When methods
}
}
void Record(object @event) {
_events.Add(@event);
}
void When(DeliveryManifestAssigned @event) {
_manifest = new DeliveryManifest(this);
_manifest.Initialize(@event);
}
void When(DeliverManifestChanged @event) {
_manifest.Initialize(@event);
}
}
public class DeliveryManifest {
Dictionary<Type, Action<object>> _handlers = new Dictionary<Type, Action<object>>();
DeliveryRun _run;
public DeliveryManifest(DeliveryRun run) {
_run = run;
Register<DeliveryManifestAssigned>(When);
Register<DeliveryManifestChanged>(When);
}
public void Initialize(object @event) {
Play(@event);
}
public void Change(...) {
Apply(new DeliveryManifestChanged(...));
}
void Register<T>(Action<T> handler) {
_handlers.Add(typeof(T), @event => handler((T)@event));
}
void Play(object @event) {
Action<object> handler;
if(_handlers.TryGet(@event.GetType(), out handler)) {
handler(@event); //dispatches to those When methods
}
}
void Apply(object @event) {
_run.NotifyOf(@event);
}
void When(DeliveryManifestAssigned @event) {
//...
}
void When(DeliveryManifestChanged @event) {
//...
}
}
P.S. I coded this "out of my head", please forgive me for the compilation errors.
Upvotes: 0
Reputation: 816
I would avoid making DeliveryManifest
another Aggregate Root unless it's a consistency boundary.
Many samples don't deal with this problem. It seems like it should be the responsibility of the aggregate root to collect events from entities inside it, and to distribute them to the correct entities for loading later on, which seems to be your option 1.
Option 2 is also perfectly good if there's no behaviour associated with the DeliveryManifest
.
Upvotes: 2