Let's play everyone's favorite game, find the Aggregrate Root. Let's use the canonical Customer/Order/OrderLines/Product problem domain. Traditionally, Customer, order, and product are the AR's with OrderLines being entities under the Order. The logic behind this is that you need to identify customers, orders, and products, but an OrderLine wouldn't exist without an Order. So, in our problem domain, we have a business rule saying that a Customer can only have one undelivered order at a time.
Does that move the order under the customer aggregate root? I think it does. But in doing so, that makes the Customer AR rather large and subject to concurrency issues later.
Or, what if we had a business rule stating that a customer can only order a particular product once in its lifetime. This is more evidence requiring the Customer to own the Order.
But when it comes to shipping, they do all of their actions on the Order, not the customer. It's kind of dumb to have to load up the entire customer in order to mark an individual Order as delivered.
This is what I'm proposing:
class Customer
{
public Guid Id {get;set;}
public string Name { get; set; }
public Address Address { get; set; }
public IEnumerable<Order> Orders { get; set; }
public void PlaceOrder(ThingsInTheOrder thingsInTheOrder)
{
// Make sure there aren't any pending orders already.
// Make sure they aren't ordering a Widget if they've already ordered a Widget in the past.
// Create an Order object and add it to the collection. Raise a domain event to trigger emails and other stuff
}
}
class Order
{
public Guid Id { get; set; }
public IEnumerable<OrderLine> OrderLines { get; set; }
public ShippingData {get;set;}
public void Ship(ShippedByPerson shippedByPerson, string trackingCode)
{
// Create a new ShippingData object and assign it from the data passed in.
// Publish a domain event
}
}
My biggest concern is the concurrency issue and the fact that the Order itself has characteristics of an aggregate root.
Best Answer
What's the criteria for defining an aggregate ?
Let's go back to the basics of the big blue book:
The goal is to maintain the invariants. But it's also to manage properly local identity, i.e. the identify of objects which do not have a meaning alone.
Order
andOrder line
definitively belong to such a cluster. For example:Order
, will require deletion of all its lines.So here the full aggregate is required to ensure consistency rules and invariants.
When to stop ?
Now, you describe some business rules, and argue that to ensure them, you'd need to consider the customer as part of the aggregate:
Of course, why not. Let's see the implications: the order would always be accessed via the customer. Is this real life ? When workers are filling the boxes for delivering the order, will they need to read the customer bar code and the order barcode to access the order ? In fact, in general, the identity of an Order is global not local to a customer, and this relative independence suggests to keep him outside the aggregate.
In addition, these business rules look more as policies: it's an arbitrary decision of the company to run their process with these rules. If the rules are not respected, the boss might be unhappy, but the data is not really inconsistent. And moreover, overnight "per customer one undelivered order at a time" could become "ten undelivered orders per customer" or even "independently of the customer, hundred undelivered orders per warehouse", so that the aggregate might no longer be justified.