Strengthening your domain: Aggregate Construction


Our application complexity has hit its tipping point, and we decide to move past anemic domain models to rich, behavioral models.  But what is this anemic domain model?  Let’s look at Fowler’s definition, now over 6 years old:

The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.

For CRUD applications, these “domain services” should number very few.  But as the number of domain services begins to grow, it should be a signal to us that we need richer behavior, in the form of Domain-Driven Design.  Building an application with DDD in mind is quite different than Model-Driven Architecture.  In MDA, we start with database table diagrams or ERDs, and build objects to match.  In DDD, we start with interactions and behaviors, and build models to match.  But one of the first issues we run into is, how do we create entities in the first place?  Our first unit test needs to create an entity, so where should it come from?

Creating Valid Aggregates

Validation can be a tricky beast in applications, as we often see validation has less to do with data than it does with commands.  For example, a Person might have a required “BirthDate” field on a screen.  But we then have the requirement that legacy, imported Persons might not have a BirthDate.  So it then becomes clear that the requirement of a BirthDate depends on who is doing the creation.

But beyond validation are the invariants of an entity.  Invariants are the essence of what it means for an entity to be an entity.  We may ask our customers, can a Person be a Person (in our system) without a BirthDate?  Yes, sometimes.  How about without a Name?  No, a Person in our system must have some identifying features, that together define this “Person”.  An Order needs an OrderNumber and a Customer.  If the business got a paper order form without customer information, they’d throw it out!  Notice this is not validation, but something else entirely.  We’re asking now, what does it mean for an Order to be an Order?  Those are its invariants.

Suppose then we have a rather simple set of logic.  We indicate our Orders as “local” if the billing province is equal to the customer’s province.  Rather easy method:

public class Order
{
    public bool IsLocal()
    {
        return Customer.Province == BillingProvince;
    }

I simply interrogate the Customer’s province against the Order’s BillingProvince.  But now I can get myself into rather odd situations:

[Test]
public void Should_be_a_local_customer_when_provinces_are_equal()
{
    var order = new Order
    {
       BillingProvince = "Ontario"
    };
    var customer = new Customer
    {
        Province = "Ontario"
    };

    var isLocal = order.IsLocal();

    isLocal.ShouldBeTrue();
}

Instead of a normal assertion pass/fail, I get a NullReferenceException!  I forgot to set the Customer on the Order object.

But wait – how was I able to create an Order without a Customer?  An Order isn’t an Order without a Customer, as our domain experts explained to us.  We could go the slightly ridiculous route, and put in null checks.

But wait – this will never happen in production.  We should just fix our test code and move on, right?  Yes, I’d agree if you were building a transaction-script based CRUD system (the 90% case).  However, if we’re doing DDD, we want to satisfy that requirement about aggregate roots, that its invariants must be satisfied with all operation.  Creating an aggregate root is an operation, and therefore in our code “new” is an operation.  Right now, invariants are decidedly not satisfied.

Let’s modify our Order class slightly:

public class Order
{
    public Order(Customer customer)
    {
        Customer = customer;
    }

We added a constructor to our Order class, so that when an Order is created, all invariants are satisfied.  Our test now needs to be modified with our new invariant in place:

[TestFixture]
public class Invariants
{
    [Test]
    public void Should_be_a_local_customer_when_provinces_are_equal()
    {
        var customer = new Customer
        {
            Province = "Ontario"
        };
        var order = new Order(customer)
        {
            BillingProvince = "Ontario"
        };

        var isLocal = order.IsLocal();

        isLocal.ShouldBeTrue();
    }
}

And now our test passes!

###

Summary and alternatives

Going from a data-driven approach, this will cause pain.  If we write our persistence tests first, we run into “why do I need a Customer just to test Order persistence?”  Or, why do we need a Customer to test order totalling logic?  The problem occurs further down the line when you start writing code against a domain model that doesn’t enforce its own invariants.  If invariants are only satisfied through domain services, it becomes quite tricky to understand what a true “Order” is at any time.  Should we always code assuming the Customer is there?  Should we code it only when we “need” it?

If our entity always satisfies its invariants because its design doesn’t allow invariants to be violated, the violated invariants can never occur.  We no longer need to even think about the possibility of a missing Customer, and can build our software under that enforced rule going forward.  In practice, I’ve found this approach actually requires less code, as we don’t allow ourselves to get into ridiculous scenarios that we now have to think about going forward.

But building entities through a constructor isn’t the only way to go.  We also have:

The bottom line is – if our entity needs certain information for it to be considered an entity in its essence, then don’t let it be created without its invariants satisfied!

AutoMapper for Silverlight 3.0 Alpha