Find the DDD Aggregate Root

domain-driven-design

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:

Aggregate: A cluster of associated objects that are treated as a unit for the purpose of data changes. External references are restricted to one member of the AGGREGATE, designated as the root. A set of consistency rules applies within the AGGREGATE’S boundaries.

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 and Order line definitively belong to such a cluster. For example:

  • Delete an Order, will require deletion of all its lines.
  • Deleting a line might require renumbering of the following lines
  • Adding a new line would require to determine the line nulber based on all the other lines of the same order.
  • Changing some order information, such as for example the currency, might affect the meaning of the price in the line items (or require to recalculate the prices).

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:

We have a business rule saying that a Customer can only have one undelivered order at a time.

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.