Strengthening your domain: Encapsulating operations


Other posts in this series:

In previous posts, we walked through the journey from an intentionally anemic domain model (one specifically designed with CRUD in mind), towards a design of a stronger domain model design.  Many of the comments on twitter and in the posts noted that many of the design techniques are just plain good OO design.  Yes!  That’s the idea.  If we have behavior in our system, it might not be in the right place.  The DDD domain design techniques are in place to help move that behavior from services surrounding the domain back into the domain model where it belongs.

Besides managing the creation of aggregates and relationships inside and between aggregate roots, there comes the point where we need to actually…update information in our domain model.  One of the tipping points from moving from a CRUD model to a DDD model is emergent complexity in updating information in our model.

Fees and Payments

Let’s suppose that in our domain we can levy Fees against Customers.  Later, Customers can make Payments on those Fees.  At any time, I can look up a Fee and determine the Fee’s balance.  Or, I can look up all the Customer’s Fees, and see a list of all the Fee’s balances.  Based on the previous posts, I might end up with something like this:

[Test]
public void Should_apply_the_fee_to_the_customer_when_charging_a_customer_a_fee()
{
    var customer = new Customer();
    
    var fee = customer.ChargeFee(100m);

    fee.Amount.ShouldEqual(100m);

    customer.Fees.ShouldContain(fee);
}

The ChargeFee method is rather straightforward:

public Fee ChargeFee(decimal amount)
{
    var fee = new Fee(amount, this);

    _fees.Add(fee);

    return fee;
}

Next, we want to be able to apply a payment to a fee:

[Test]
public void Should_be_able_to_record_a_payment_against_a_fee()
{
    var customer = new Customer();

    var fee = customer.ChargeFee(100m);

    var payment = fee.AddPayment(25m);
    fee.RecalculateBalance();

    payment.Amount.ShouldEqual(25m);

    fee.Balance.ShouldEqual(75m);
}

We store a calculated balance, as this gives us much better performance, querying abilities and so on.  It’s rather easy to make this test pass:

public Payment AddPayment(decimal paymentAmount)
{
    var payment = new Payment(paymentAmount, this);

    _payments.Add(payment);

    return payment;
}

public void RecalculateBalance()
{
    var totalApplied = _payments.Sum(payment => payment.Amount);
    
    Balance = Amount - totalApplied;
}

However, our test looks rather strange at this point.  We have a method to establish the relationship between Fee and Payment (the AddPayment method), which acts as a simple facade over the internal list.  But what’s with that extra “RecalculateBalance” method?  In many codebases, that RecalculateBalance method would be in a BalanceCalculationService, reinforcing an anemic domain.

But we can do one better.  Isn’t the act of recording a payment a complete operation? In the real physical world, when I give a person money, the entire transaction is completed as a whole.  Either it all completes successfully, or the transaction is invalid.  In our example, how can I add a payment and the balance not be immediately updated?  It’s rather confusing to have to “remember” to use these extra calculation services and helper methods, just because our domain objects are too dumb to handle it themselves.

Thinking with commands

When we called the AddPayment method, we left our Fee aggregate root in an in-between state.  It had a Payment, yet its balance was incorrect.  If Fees are supposed to act as consistency boundaries, we’ve violated that consistency with this invalid state.  Looking strictly through a code smell standpoint, this is the Inappropriate Intimacy code smell.  Inappropriate Intimacy is one of the biggest indicators of an anemic domain model.  The behavior is there, but just in the wrong place.

But we can help ourselves to enforce those aggregate boundaries with encapsulation.  Not just encapsulation like properties encapsulate fields, but encapsulating the operation of recording a fee.  Even the name of “AddPayment” could be improved, to “RecordPayment”.  The act of recording a payment in the Real World involves adding the payment to the ledger and updating the balance book.  If the accountant solely adds the payment to the ledger, but does not update the balance book, they haven’t yet finished recording the payment.  Why don’t we do the same?  Here’s our modified test:

[Test]
public void Should_be_able_to_record_a_payment_against_a_fee()
{
    var customer = new Customer();

    var fee = customer.ChargeFee(100m);

    var payment = fee.RecordPayment(25m);

    payment.Amount.ShouldEqual(25m);

    fee.Balance.ShouldEqual(75m);
}

We got rid of that pesky, strange extra “Recalculate” call, and now encapsulated the entire command of “Record this payment” to our aggregate root, the Fee object.  The RecordPayment method now encapsulates the complete operation of recording a payment, ensuring that the Fee root is self-consistent at the completion of the operation:

public Payment RecordPayment(decimal paymentAmount)
{
    var payment = new Payment(paymentAmount, this);

    _payments.Add(payment);

    RecalculateBalance();

    return payment;
}

private void RecalculateBalance()
{
    var totalApplied = _payments.Sum(payment => payment.Amount);
    
    Balance = Amount - totalApplied;
}

Notice that we’ve also made the Recalculate method private, as this is an implementation detail of how the Fee object keeps the balance consistent.  From someone using the Fee object, we don’t care how the Fee object keeps itself consistent, we only want to care that it is consistent.

The public contour of the Fee object is simplified as well.  We only expose operations that we want to support, captured in the names of our ubiquitous language.  The “How” is encapsulated behind the aggregate root boundary.

Wrapping it up

We again see that a consistent theme in DDD is good OO and attention to code smells.  DDD helps us by giving us patterns and direction, towards placing more and more logic inside our domain.  The difference between an intentionally anemic domain model (a persistence model) and an anemic domain model is the presence of these code smells.  If you don’t have a legion of supporting services propping up the state of your domain model, then there’s no problem.

However, it’s these external domain services where we are likely to find the bulk of domain model smells.  Through attention to the code smells and refactorings Fowler laid out, we can move towards the concepts of self-consistent aggregate roots with strongly-enforced boundaries.

No silver domain modeling bullets