Can clients call methods on entities other than the aggregate root

domain-driven-design

Evans introduces in his book "Domain Driven Design" in Chapter 6 "Aggregates" the concept of Aggregates. He further defines rules to translate that concept into an implementation (Evans 2009, pp. 128-129):

The root ENTITY can hand references to the internal ENTITIES to other objects, but those objects can use them only transiently, and they may not hold on to the reference.

After elaborating on other rules he summarizes them into this paragraph:

Cluster the Entities and Value Objects into Aggregates and
define boundaries around each. Choose one Entity to be the root
of each Aggregate, and control all access to the objects inside
the boundary through the root. Allow external objects to hold
references to the root only. Transient references to internal
members can be passed out for use within a single operation
only.
Because the root controls access, it cannot be blindsided by
changes to the internals. This arrangement makes it practical to
enforce all invariants for objects in the Aggregate and for the
Aggregate as a whole in any state change.

So what does transient usage exactly mean?

My colleague understands that only the aggregate root exposes a public interface for the clients. Clients will have no opportunity to call any operation on an entity other than the aggregate root.

My understanding of the cited sentences is different. I understand that it does indeed explicitly allow clients calling operations on internal entities. However only after getting them from the root.

So let's have a concrete example:

Let's say a Cart consists of many Items. Each Item has a Quantity. The model should support the use case "Increase the quantity of one specific Item". No invariants could be violated which affects anything outside of the Item.

Is a model violating above cited rules, when a client can do this by calling cart.item(itemId).increaseQuantity() or should a client only be allowed to call a cart.increaseItemQuantity(itemId)? What would be the benefit of the latter?

Best Answer

As long as Item cannot exist without Cart also being present, then there is no difference between the two options. It is possible to keep invariants in both cases.

In the case of method being on Item, the Item can "notify" it's parent cart to check the invariant when it needs to change it's own state. This makes things slightly more complicated, because then there is cyclical dependency between Item and Cart (which I assume is not a problem thanks to assumption in first sentence and something that IMO needs to exist either way).

In case of method being on Cart, it makes it simper, because there is no need for Item to reference Cart. But it makes it complicated because now the method not only checks for invariant and does the state change. But it also needs to ensure the item (or it's ID) is valid for this Cart. In the other case, this is already handled by method that queries given item from cart.

tl;dr; Both options have clear advantages and disadvantages and neither seems to be obviously better or worse than other.