DDD Aggregate Root with Complex Aggregates – Best Practices

domain-driven-design

I have an AR and I can't figure out the best way to create/edit some of the complex aggregates contained in it. These aggregates have many parameters so I decided within the domain model to use Value Objects instead of primitive types.

public class Load : AggregateRoot
{
    public virtual string Name { get; protected set; }

    public virtual User ResponsibilityOf { get; protected set; }

    public virtual User AssignedTo { get; protected set; }

    public virtual User CoveredBy { get; protected set; }

    public virtual State State { get; protected set; }

    public virtual Cargo Cargo { get; protected set; }

    public virtual Guid CustomerId { get; protected set; }

    public virtual IList<Run> Runs { get; protected set; }

    public Load(Guid customerId, string name) : this()
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentNullException(nameof(name));

        Name = name;
        CustomerId = customerId;
        Runs = new List<Run>();
    }
}

Runs is a collection of Value Objects and Run is defined below. It's a good example of the issue I'm facing because it takes several parameters and some of them are Value Objects.

public sealed class Run : ValueObject<Run>
{
    public int Order { get; private set; }

    public Location Origin { get; private set; }

    public Location Destination { get; private set; }

    public Guid CarrierId { get; private set; }

    private Run() { }

    public Run(Guid carrierId, Location origin, Location destination, int order = 0) : this()
    {
        if (carrierId == Guid.Empty)
            throw new ArgumentNullException(nameof(carrierId));

        Origin = origin ?? throw new ArgumentNullException(nameof(origin));
        Destination = destination ?? throw new ArgumentNullException(nameof(destination));
        Order = order;
        CarrierId = carrierId;
    }
}

I want to add methods for CRUD operations for Runs to the AR (Load) and I don't know how to approach it. This would be a trivial task if Origin and Destination were primitives or they only took one or two parameters to instantiate. But Origin and Destination are Value Objects.

public sealed class Location: ValueObject<Location>
{
    public string Name { get; private set; }

    public Address Address { get;private  set; }

    public string Notes { get; private set; }

    public string Directions { get; private set; }

    public int CustomerId { get; private set; }

    public string HoursOfOperation { get; private set; }

    public Phone Phone { get; private set; }
}

So when I go to add my CRUD methods to the Aggregate Root Load, I'm lost on how best to achieve this goal. I could add the following, but it doesn't seem like the DDD way to solve this problem because outside of the domain should not know what Run is right?

void AddRun(Run run);
void RemoveRun(Run run);
void UpdateRun(Guid id, Run run );

If outside of the domain doesn't know what Run is, I would have to use primitive types as parameters and that would get messy really fast because there would need to be so many parameters to build up the object.

Edit 1:
Corrected a few things pointed out in answers.

Best Answer

I have an AR and I can't figure out the best way to create/edit some of the complex aggregates contained in it.

Caution: your spelling is off. AR don't contain aggregates, the naming is the other way around.

An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary.

Conceptually, the aggregate is a graph of (possibly complex) entities, with the aggregate root being one of those entities in the graph.

Also: value objects are usually immutable - you shouldn't have setters.

So when I go to add my CRUD methods to the Aggregate Root Load, I'm lost on how best to achieve this goal. I could add the following, but it doesn't seem like the DDD way to solve this problem because outside of the domain should not know what Run is right?

There are a couple possible answers to the riddle.

In this model, you seem to have a lot of properties that you need to reference to get anything done. So something like a http message that describes a change is going to have a complicated json document.

One way to turn that document into something that the model understands is to use a builder pattern. You give the application little mini factories to build up the data structures that it needs to talk to the model. That's a perfectly reasonable thing to do.

Another possible answer is to examine your model more carefully; domain models are primarily about change; they aren't glorified document stores. Information that the model doesn't need should be stored in a document somewhere else. For example, if a Run is a response to an "Order", then the Run might have a copy of the order number and a copy of the data that it actually needs to do its work, but not everything else.

(In the same vein: if you have a CustomerId in your value, you probably don't also need a Customer value. Use the id to look up the details you don't normally use.)

Unfortunately most of the Run information will be needed by the model.

If that's so, then it is, and you'll have to manage it. But push back on that assumption -- if you confuse the data you need to make a decision with the data you need to create a view, you are going to end up making more work for yourself.

Would the factories go in the domain layer?

It's a reasonable first guess - we usually start out with an assumption of one implicit collection of agnostic primitives (whatever language we happen to be coding in at the time). Evans coupled aggregates, repositories, and factories together in chapter 6 of the blue book, considering them all to be part of the domain layer.

I think it's a bit more correct to think of these factories as part of an adapter that allows the application to communicate with the model.