When working on a new domain model a few days ago, we ran into an interesting issue. What happens when an aggregate has shared data? You know, data that is common or shared between aggregate roots? This is a question that also recently came up on the Domain Driven Design Yahoo Group, we had generally the same question but independent of the Yahoo Group.
For purposes of example, we will use the canonical sales/order model. Let's suppose that there is a business rule which states that unconfirmed accounts may only place a maximum of two orders which combined total no more than $100 in order to limit fraud. It's a simple enough business requirement, right? But there is a small challenge, this particular business requirement seems to span multiple aggregate roots which each contain additional business rules surrounding the acceptance of an order.
So what do you do? First of all, from a technical perspective, it can be solved several different ways. But the reason that we're working with a domain model is because we want the benefits of a domain model. In order to get those benefits we have to follow the rules of a domain model.
The discussion on the Yahoo Group surrounded that of using double-dispatch techniques in order to retrieve the data needed by the aggregate root. Ever since attending Udi's SOA course in Austin last December, I generally dislike using double-dispatch techniques within the domain model.
Another simple technique is to have a shared entity object between the aggregates. While it works and NHibernate will let you do this, it's generally a bad idea because an aggregate is a unit of consistency. It is atomic—all or nothing. Having a shared entity violates that atomicity and autonomy of an aggregate. While technically speaking we can do this, we probably shouldn't as it violates one of the fundamental principles of DDD.
It may sound silly but at this point we were a little bit stuck. When using event sourcing to rebuild your Order aggregate, it has no concept of the combined order total of all orders for a customer. It only cares about itself and not other orders. So how do we enforce the business requirement? We don't like the idea of double dispatch and we don't want to have some kind of shared entity. What can we do?
The answer is to introduce another aggregate—an OrderTotals aggregate. When the PlaceOrderCommand is dispatched to system, it is handled by two aggregates within the service layer. The first aggregate, OrderTotals, ensures that the command can be processed. If not, it returns a fault indicating why. If we receive a fault, we stop processing. Otherwise, the Order aggregate receives the command and processes it. When finished, it publishes the OrderAccepted event (to which the OrderTotals aggregate subscribes) and the transaction commits.
All in all, it's fairly simple. This idea applies beyond situations where event sourcing is being used to rebuild the domain entities by replaying the events. It addresses the fundamental issue of how you deal with rules that span multiple aggregate roots.