4

I´m trying to understand how to represent certain DDD (Domain Driven Design) rules. Following the Blue Book convention we have:

  • The root Entity has global identity and is responsible for checking invariants.
  • The root entity controls access and cannot be blindsided by changes to its internals.
  • Transient references to internal members can be passed out for use withing a single operation only.

I´m having a hard time to find the best way to enforce the invariants when clients can have access to internal entities.

This problem of course only happens if the child entity is mutable.

Supose this toy example where you have a Car with four Tire(s). I want to track the usage of each Tire idependently.

Clearly Car is a Aggregate Root and Tire is an Child Entity.

Business Rule: Milage cannot be added to to a single Tire. Milage can only be added to all 4 tires, when attached to a Car

A naive implementation would be:

public class Tire
{
    public double Milage { get; private set;  }
    public DateTime PurchaseDate { get; set; }
    public string ID { get; set; }
    public void AddMilage(double milage) => Milage += milage;
}

public class Car
{
    public Tire FrontLefTire { get; private set; }
    public Tire FrontRightTire { get; private set; }
    public Tire RearLeftTire { get; private set; }
    public Tire RearRightTire { get; private set; }

    public void AddMilage (double milage)
    {
        FrontLefTire.AddMilage(milage);
        FrontRightTire.AddMilage(milage);
        RearLeftTire.AddMilage(milage);
        RearRightTire.AddMilage(milage);
    }

    public void RotateTires()
    {
        var oldFrontLefTire = FrontLefTire;
        var oldFrontRightTire = FrontRightTire;
        var oldRearLeftTire = RearLeftTire;
        var oldRearRightTire = RearRightTire;

        RearRightTire = oldFrontLefTire;
        FrontRightTire = oldRearRightTire;
        RearLeftTire = oldFrontRightTire;
        FrontLefTire = oldRearLeftTire;
    }

    //...
}

But the Tire.AddMilage method is public, meaning any service could do something like this:

Car car = new Car(); //...

// Adds Milage to all tires, respecting invariants - OK
car.AddMilage(200); 

//corrupt access to front tire, change milage of single tire on car
//violating business rules - ERROR
car.FrontLefTire.AddMilage(200); 

Possible solutions that crossed my mind:

  1. Create events on Tire to validate the change, and implement it on Car
  2. Make Car a factory of Tire, passing a TireState on its contructor, and holding a reference to it.

But I feel there should be an easier way to do this.

What do you think ?

Fabio Marreco
  • 2,186
  • 2
  • 23
  • 24

2 Answers2

2

Transient references to internal members can be passed out for use withing a single operation only.

In the years since the blue book was written, this practice has changed; passing out references to internal members that support mutating operations is Not Done.

A way to think of this is to take the Aggregate API (which currently supports both queries and commands), and split that API into two (or more) interfaces; one which supports the command operations, and another that supports the queries.

The command operations still follow the usual pattern, providing a path by which the application can ask the aggregate to change itself.

The query operations return interfaces that include no mutating operations, neither directly, nor by proxy.

root.getA() // returns an A API with no mutation operations
root.getA().getB() // returns a B API with no mutation operations

Queries are queries all the way down.

In most cases, you can avoid querying entities altogether; but instead return values that represent the current state of the entity.

Another reason to avoid sharing child entities is that, for the most part, the choice to model that part of the aggregate as a separate entity is a decision that you might want to change in the domain model. By exposing the entity in the API, you are creating coupling between that implementation choice and consumers of the API.

(One way of thinking of this: the Car aggregate isn't a "car", it's a "document" that describes a "car". The API is supposed to insulate the application from the specific details of the document.)

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • What about methods that express business behavior ? they will almost certainly have side-efects. Where would you put then ? At the api ? isn´t that an *anemic* model ? – Fabio Marreco Nov 28 '17 at 17:05
  • 1
    @FabioMarreco I think you misunderstood this answer. Think of it as `class Car implements ImmutableCar, MutableCar`. The API IS the aggregate, it's not another class. I'm not sure how to reconcile the Ubiquitous Language with the approach, but that's the spirit I think. – plalx Nov 28 '17 at 22:27
  • @plalx, Sorry... misunderstood the answer, (I guess I was sleepy). It does Makes sense. – Fabio Marreco Nov 29 '17 at 11:21
  • Would the *return values represent current state* solution look like this? : https://github.com/fabiomarreco/ddd-tests/blob/master/EntityScope/WithValueObjects.cs – Fabio Marreco Nov 29 '17 at 11:39
  • Using a readonly interface is easier, but I had devs typecasting to access mutating methods (i know, iknow...) . So I usually prefer something more robust. – Fabio Marreco Nov 29 '17 at 11:47
  • 1
    An immutable internal state would work, but could potentially harm performance for large states I guess because the aggregate must replace it all for any kind of mutation. The ISP-based solution is not exposed to such problem, but like you said it's easier to bypass by type-casting. However at this point if devs want to screw you up they always can... they could use reflection to mutate the state anyway. – plalx Nov 29 '17 at 13:46
  • When devs are forced to think, they´ll try to find a better solution. if it is too easy, they´ll just hack it. But you´re right, I should be more optimistic about people ;) – Fabio Marreco Nov 29 '17 at 17:28
0

There should be no getters for the Tires.

Getters get you in trouble. Removing the getters is not just a matter of DDD Aggregte Roots, but a matter of OO, Law of Demeter, etc.

Think about why you would need the Tires from a Car and move that functionality into the Car itself.

Robert Bräutigam
  • 7,514
  • 1
  • 20
  • 38
  • That It is against Eric Evens´ blue book: *Transient references to internal members can be passed out for use withing a single operation only.* – Fabio Marreco Nov 28 '17 at 16:46
  • I would probably do that if the aggregate is a simple, but if I start adding other child entities to it (engine, door, windshiled, etc), it will turn the *aggregate* into a *façade* (very cumbersome). – Fabio Marreco Nov 28 '17 at 16:51
  • From a client perspective, It´s no longer an *aggregate* of entities (I guess not exactly a problem, just sayin´) – Fabio Marreco Nov 28 '17 at 16:53