Does DDD promote large Aggregates?

Recently, we had a presentation at my workplace about Domain-Driven Design concepts, given by my team member. He tried to explain the main thoughts behind it and gave some real world examples to make it easier for the spectators to grasp them. After the presentation, I’ve noticed some confusion among the audience about actual usage and implementation of Aggregates. In this post, I’ll try to answer two of the questions that my colleague received from the audience with more detail. Note, that to be able to understand this post, you should be familiar with basic DDD terms.

Background

The presentation took car manufacturing as the sample domain. This is a good choice, since most of us deal with cars every day and can relate to the domain in one way or another. However, imagining a Car Aggregate with all it’s many bits and pieces creates some confusion for seasoned developers. What about performance? What about enormous size of the object graph?

Before going any further, I think I should put more stress on what an Aggregate Root is. An Aggregate Root (AR) is a core entity in the Aggregate that is responsible for ensuring business rules across all parts of that Aggregate. It has a unique ID that lets you differentiate between other instances of the same type. Looking at the car example, Car would certainly be an AR, having a unique ID (VIN) and being responsible for ensuring that all its parts integrate correctly. It is by no means just a container with a single purpose of forwarding calls to other entities in the Aggregate.

Now, let’s move on to answering questions from the audience.

How to ensure that aggregates comply with the Single Responsibility Principle?

I guess this question was asked because of the Car example as an Aggregate Root. If we look from the real world perspective, a car is a very complex object, consisting of many different parts, so it’s natural to suspect that an implementation of this object in code would violate the SRP (Single Responsibility Principle). Indeed, an Aggregate Root might violate SRP by having different responsibilities – identification, ensuring different business rules, etc… However, the Aggregate design rules that DDD describes make it harder to abuse those different responsibilities. An Aggregate Root’s primary responsibility is protecting it’s invariants or, in other words, ensuring that the business rules are enforced inside the Aggregate boundary. The classes should have only as much code as needed to ensure that. They should also be as small as possible to avoid concurrency and performance issues. In an ideal case, the Aggregate consists of only a single entity – the Root itself. So, an Aggregate as large as a Car would not even be possible, if designed right.

As you see, there are trade-offs involved here. Possible violations of SRP do not defeat the benefit of proper encapsulation and business rule enforcement. At the very least, Aggregates are not any worse than other object-oriented code to deal with.

If a Car is an AR, does it mean that if you want to change a light bulb in a car you should access it through the Car AR?

First of all, Aggregate design highly depends on the context. Take Car Servicing as the context and let’s try to model the situation:

Changing a light bulb involves doing some access to a car’s lamp, right? First question to ask is: can different people repair/change parts of the same car at the same time? The answer in the real world is probably “yes” – it’s what can make the business more effective. To us it means that a CarLamp can be modified independently of other parts of a Car object – meaning it’s an AR by itself. Let’s drill down. If we look at changing the light bulb of that car lamp, things get a bit different. Can a light bulb be changed at the same time as someone is changing the socket of the same lamp? Hardly, you would probably need to wait for the socket to be changed first. It’s a concurrency restriction, only one client is allowed to change the Aggregate at once. Thus, we can arrive at the conclusion that there is a separate Aggregate consisting of CarLamp, LightBulb and Socket entities and the CarLamp is the Root of this Aggregate. This Aggregate is much smaller, but is still able to enforce invariants within its bounds.

So, to change a light bulb in a car, you would access the CarLamp aggregate directly, without a need to retrieve or modify a Car Aggregate. Of course, the CarLamp will belong to a Car in some way. Maybe the “Car” Aggregate has a list of CarLamp references; maybe CarLamp has a property called CarId. Relationships between Aggregates is not the topic for this post, so I will skip the details here.

This might seem a little counter-intuitive. However, it is the core of DDD modelling. Don’t try to model this as in the real world, but instead model Aggregates based on consistency requirements of your business. Trying to implement the real world in code will make your models large and eventually you’ll hit performance problems. As Greg Young likes to ask, does your model have a Root called Universe?

Manipulating objects inside an Aggregate

Contrary to what I think that people picked up during the presentation, not every action on the Aggregate must be invoked through its Root. It all comes down to the consistency rules of the Aggregate. As already mentioned, an Aggregate is a consistency boundary, meaning that any operation done on the Aggregate components must leave the WHOLE Aggregate in a valid state at ANY given time. If this condition is satisfied, even when calling the Aggregate’s entity directly, it is completely OK to do so. Let’s take a look at an example (it’s pseudo code, so I won’t accept any complaints that it doesn’t compile :)).

Let’s say that an Order can have multiple OrderLines and there is a need to calculate the Order.Total as a sum of all OrderLine Prices. Since modifying an OrderLine without updating Order.Total would violate our business rules, both of these classes belong to the same Aggregate, with Order being it’s Root.

public class OrderLine {
   public decimal Price { get; set; }
}

public class Order {
    public OrderLine[] OrderLines = new OrderLine[]();
    public decimal Total { get; private set; }
}

To enforce the business rule, changing the OrderLine.Price implementation can be like this:

public class OrderLine {
   // limit access to the Price setter, so it could only be accessed from the Root
   public decimal Price { get; internal set; }
}

public class Order {
    public OrderLine[] OrderLines = new OrderLine[]();
    public decimal Total { get; private set; }

    public void ChangeOrderLineTotal(int productId, decimal newPrice) {
        var line = [get an order line by productId];
        line.Price = newPrice;
        Total = Sum(OrderLines);
    }
}

On the other hand, it’s completely possible to do something like this:

public class OrderLine {
    public decimal Total { get; private set; }

    // have a reference to the Aggregate Root
    public Order Order { get; }

    public void ChangeTotal(decimal newTotal) {
        Total = newTotal;

        // force the parent to recalculate it's total
        Order.RecalculateTotal();
    }
}

In both cases, the business rule is enforced, so it’s completely an implementation detail. Choose what’s most effective in your programming language/framework. I’d say that less code is better code if you can still achieve the same encapsulation and consistency.

Conclusions

As we have just seen, a large Aggregate is not something that you should aim for. You should be more concerned about the invariants of the Aggregate instead of it’s size. Anyways, I’m very happy that there is an interest in applying DDD from our colleagues. I hope this post can be useful to any of you who are struggling with understanding or applying the principles in a real project.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s