Take a classic example of the difference between a simple CRUD-based
event and an event with true domain meaning: CustomerAddressCorrected
vs. CustomerMoved. Both update a customer's address, but one indicates
a typo was fixed, and one indicates that the person moved. They have
radically different business meaning.
But the raw content of the events may well be identical. No problem,
we have a common object like AddressUpdated, which is a member of both
events
AddressUpdated: { Street:string; State:enum } // etc
CustomerAddressCorrected: { CustomerId:CustomerId;
AddressUpdated:AddressUpdated }
CustomerMoved: { CustomerId:CustomerId; AddressUpdated:AddressUpdated}
This isn't particularly difficult, but it does cause what might be
considered laborious investigation of the domain model (as exposed in
its events) to see this duplicated data. For instance, a read model
projection which was only concerned with tracking the customer's
current address would have to match on both CustomerAddressCorrected
and CustomerMoved, pull the same object out of both, and hand it to a
similar handler.
But a concept I see trafficked with increasing regularity is the
notion that domain events come in self-consistent batches, e.g.
Commits. One command handler might emit multiple events, and those
events should be considered together, or not at all. It's easy to
understand this if the events appear to be totally unrelated
(CustomerMoved and AccountClosed), but why not take advantage of this
to simplify our modeling language? What if we just had
AddressUpdated: { CustomerId:CustomerId; Street:string;
State:enum } // etc
CustomerAddressCorrected: { CustomerId:CustomerId; }
CustomerMoved: { CustomerId:CustomerId; }
Where a single commit from the "MoveCustomer" command would contain
both the "CustomerMoved" event and the "AddressUpdated" event?
Obviously the "CustomerMoved" event no longer stands on its own, but
we admitted as much when we allowed events to travel in packs anyway.
I can see pros and cons to both approaches. The former approach seems
to result in significant redundancy in events which accomplish similar
things but come from different stimuli. The latter approach likely
makes projections easier to write as the events are simpler and less
repetitive. The latter approach may too easily lead to a degradation
of everything to a simple list of CRUD commands.
(Note, I am still assuming this is the event stream of a single
aggregate root. On our project at least, we don't really make it
possible to fold together events from multiple aggregates into a
single commit, which is essentially how we ensure each aggregate is a
transactional boundary)
What are peoples' experience with relying on multiple domain events to
capture a transactional change to an aggregate root?
Take a classic example of the difference between a simple CRUD-based
event and an event with true domain meaning: CustomerAddressCorrected
vs. CustomerMoved. Both update a customer's address, but one indicates
a typo was fixed, and one indicates that the person moved. They have
radically different business meaning.
But the raw content of the events may well be identical. No problem,
we have a common object like AddressUpdated, which is a member of both
events
AddressUpdated: { Street:string; State:enum } // etc
CustomerAddressCorrected: { CustomerId:CustomerId;
AddressUpdated:AddressUpdated }
CustomerMoved: { CustomerId:CustomerId; AddressUpdated:AddressUpdated}
to simplify our modeling language? What if we just had
AddressUpdated: { CustomerId:CustomerId; Street:string;
State:enum } // etc
CustomerAddressCorrected: { CustomerId:CustomerId; }
CustomerMoved: { CustomerId:CustomerId; }
Where a single commit from the "MoveCustomer" command would contain
both the "CustomerMoved" event and the "AddressUpdated" event?