Open MerrionComputing opened 4 years ago
Since the definitive definition relies a lot on "consistency boundary" it would also be helpful to have a definition of what that means.
In my experience Aggregate is often said as shorthand when people actually mean Aggregate Root causing all sorts of confusion when the difference isn't clear. The root is used to hold the references to various Entities contained in the Aggregate and helps define the boundaries for the Aggregate. The crux is that the Aggregate defines the scope of an atomic commit boundary for both loading and saving to persistent storage. In the code this generally means that the entire Aggregate is loaded from a repository by the the Id of the Aggregate Root Entity. In this pattern it is not safe to use data from anything outside of the Aggregate when enforcing business rules on state changes to the aggregate, as that data is outside of the atomic commit boundary will not be guaranteed to be consistent with the state of the Aggregate. This limitation while at times quite frustrating is the golden rule for defining a good domain model. When you can model the business process as a set of decisions and operations that are independently validatable and committable you're well on your way to a really good Domain Model. Note: In Event sourced systems this generally means Stream per Aggregate
This is how I understand it: The "consistency boundary" of "aggregate" defines the order of "actual occasions" in domain model aggregates to have serial order. An aggregate then becomes the series of actual occasions it creates, plus the fixed "genetics" that functions both to create its actual occasions (commands) and to make sense of them as a whole (mutators). In terms of Whitehead's system: if a domain model is a world, and an actual world is built up of actual occasions (with the actual occasions sometimes being coded as "domain events") then the DDD "consistency boundary" gives a domain model the order of a "corpuscular society", a society of events that has many strands of "personal order".
The useful and desired thing is to order the actual occasions in series, otherwise it's hard to see how to implement "business rules". We use counting to construct a series, and an atomic database transaction is merely one part of an implementation of counting (the other two parts being an "increment" operation, and a "uniqueness" constraint). If we could implement serial ordering without counting, or counting without database transactions, we could implement "business rules". But without the serial ordering, we can't.
Similarly, domain events objects aren't essential, they just make explicit the "actual occasions" (which are inevitable, if there are to be any kind of "changes"). Timestamps implement counting, but they count periods of time and not the actual occasions of a domain model (gaps can lead to race conditions, and clock skews can put things out of order). An alternative is to construct a contiguous sequence of integers. It's called counting, and it works, and we do it to establish "personal order" rather than primarily to know how many things have happened already. If we could establish "personal order" without counting, or without database transactions, that would also construct adequate conditions for implementing "business rules".
PS When we use an atomic database transaction to implement this serial ordering of the recording of an aggregate's "changes", the serial ordering isn't somehow broken by including other things in the atomic database transaction: tracking records, event notifications, and domain events from more than one aggregate. Obviously in some cases, including events from more than one aggregate may lead to increased contention, but that's not always the case, and sometimes it might be useful and not problematic (for example the "one root, many aggregates" situation that @condron describes). And sometimes it might be required.
Is there a difference between a business rule type of aggregate and a data consistency type of aggregate...and, if so, what do we do differently in each case?
(With both examples and counter examples)