ryanulit
ryanulit

Reputation: 5001

Protecting invariants and children of aggregate roots when implementing DDD

In my attempt to start learning about DDD, I began with a simple domain model that I will gradually build up over time. My domain in this instance is the usual order > order item in order to keep things simple and be able to add on later with invoices, etc. Here's what I have so far:

public class Order
{
    private readonly IList<OrderItem> _orderItems;

    public Guid Id { get; private set; }
    public bool Completed { get; private set; }
    public DateTime Created { get; private set; }
    public IEnumerable<OrderItem> OrderItems
    {
        get { return _orderItems; }
    }

    public Order()
    {
        Id = new Guid();
        Created = DateTime.UtcNow;
        _orderItems = new List<OrderItem>();
    }

    public void AddOrderItem(int quantity, int unitCost)
    {
        var orderItem = new OrderItem(quantity, unitCost);
        _orderItems.Add(orderItem);
    }

    public void CompleteOrder()
    {
        Completed = true;
    }
}

public class OrderItem
{
    public int Quantity { get; private set; }
    public int UnitCost { get; private set; }

    public OrderItem(int quantity, int unitCost)
    {
        Quantity = quantity;
        UnitCost = unitCost;
    }
}

I will eventually turn Quantity and UnitCost into value objects, but that isn't the important part here. As DDD preaches, we always want to protect our invariants, but I'm having a little trouble with one piece of that. From an Order, you can add a new OrderItem by calling the AddOrderItem() method and passing your quantity and unit cost.

My question now becomes what is to stop another coder from creating a new OrderItem with var orderItem = new OrderItem(1, 2)? The OrderItem constructor should probably have an Order order parameter since an OrderItem cannot exist without an Order, but again now that other coder could just call new OrderItem(new Order(), 1, 2)?

Am I missing something? Or is it just accepted that the team working on the model needs to understand the fundamentals of DDD?


Update

Thanks @theDmi, @guillaume31, @Matt as you all have provided some good points. I think it is pretty clear at this point that the repository's interface should be enough to make it clear that you can't do anything with an OrderItem created by itself. Setting the ctor for OrderItem to internal also helps to enforce this restriction as well, but it may not be needed. I plan to see what happens with or without the internal ctor. Ultimately, the reason I accepted @guillaume31's answer is the comment about the bidirectional relationships. That makes a lot of sense and I have encountered this issue in the past with EF for example, so I like the idea of keeping it unilateral as well.

Upvotes: 3

Views: 1358

Answers (2)

guillaume31
guillaume31

Reputation: 14064

"An OrderItem cannot exist without an Order" is not really an invariant. Well at least it's not an invariant in the Order aggregate. By definition, invariants only look at things that are inside one aggregate (or span across multiple ones), not things that wander around outside an aggregate.

The OrderItem constructor should probably have an Order order parameter since an OrderItem cannot exist without an Order

I wouldn't model it that way, because

  • Bidirectional relationships between entities are not recommended. It can lead to synchronization problems (A points to B but B points to something else), it's better to have unidirectional relations if you can.

  • By doing that, your ultimate goal is to put a constraint on what's happening outside an Aggregate, which is not really the point of DDD, and, as other answers have shown, dispensable. All changes in a DDD system go through an Aggregate and a Repository.

Upvotes: 5

theDmi
theDmi

Reputation: 18034

When working with DDD, all attempts to change the state of the system run through a repository, because you need to retrieve the aggregate you want to work on first. So even if someone creates objects that make no sense outside of a certain entity, they will not be able to do anything useful with it.

Regarding this problem, DDD has even advantages over CRUD-based systems: It leads to a high discoverability. First, the repository interface tells you what you can load. Then you get an aggregate, which in turn offers operations that modify the aggregate in a meaningful way.

Upvotes: 6

Related Questions