2

Lets think about the most common Aggregate Root example: Order and OrderItems (or OrderLines).

So I want a useCase called UpdateOrderItem. Given a OrderItemID and a complete OrderItemDTO in the request I want to update the OrderItem info. Following the Aggregate root pattern, OrderItems should not be handled alone. So ill need to instanciate Order. My question is: to edit a specific OrderItem should I load all OrderItems and build the complete Aggregate or can I retrieve from repository just the Order data and the specific OrderItem data and follow the update ?

2
  • 'Following the Aggregate root pattern, OrderItems should not be handled alone.' You answered your own question.
    – Rik D
    May 26, 2021 at 20:32
  • Idk if I was clear enough but I think this doesnt answer my question because I can only load the specific OrderItem thata I need to update and yet do all update operation through the Order instance (which will obviously be instantiated and used as root ). May 26, 2021 at 21:43

3 Answers 3

2

One of the biggest issues here is that there is a careful balance to strike between theoretical elegance and practical performance considerations.

From a logic perspective, it's much simpler to blindly rely on the notion that all data is available, so that you don't have to write logic to account for additional fetching or second-guessing of the possibly incomplete data set you're dealing with.
From a data perspective, you want to cut down any unnecessary data fetching as much as possible, and would prefer if the logic simply worked with an incomplete (but functionally viable) data set.

But in practice, machines have limited performance and both business priorities and workloads can lead to such a blanket "load all" behavior being cost-ineffective in terms of the required hardware.

A book on the theory of DDD and aggregate roots tends to stick to its theory rather than discussing myriad possible compromises for the sake of performance.

There is also the consideration of how you define your aggregates. Purists will argue that you only label something as an aggregate specifically when you do not object to the entire aggregate being loaded in, as the aggregate represents the indivisible "data unit".

However, there may be contextual considerations here, where your aggregate is generally loaded in its entirety, but for one particular use case (e.g. a sizeable bulk import) creates more performance issues than you can manage. It would be an overcorrection to throw out the entire aggregate concept across the board because of one solitary use case (in a sea of use cases) that doesn't quite fit the pattern.

Long story short, pattern are there to help. And when they don't help, consider not using them. But then you do lose the pattern's benefit.

My question is: to edit a specific OrderItem should I load all OrderItems and build the complete Aggregate or can I retrieve from repository just the Order data and the specific OrderItem data and follow the update ?

From a "get the job done" perspective, both options are perfectly workable implementations.

The first option retains adherence to aggregate root principles, but suffers from performance issues. The second option (temporarily) dismisses the aggregate root principle in favor of performance gains.

Pick which is most important to you: design purity or performance.

1
  • Very enlightening answer. We don't use ddd in my professional day-to-day, so I'm just studying and implementing ddd in my personal projects. Considering this, adherence to the pattern for me is the most important at the moment. It really wasn't clear to me whether the pattern required it or not. Thank you very much. May 27, 2021 at 14:43
3

I sense that there are actually two different questions here.

Let's start with the definition, from Evans (2003)

An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes.

If you are going to make a change to any part of the unit, you need the entire unit to do it. Any change made to the objects within the boundary of the aggregate happens as a consequence of passing some information to the AGGREGATE ROOT.

That's the pattern. If you aren't loading in the entire aggregate for each change, then you aren't following the pattern.


The harder question is whether "order items" should be part of the same aggregate as the other things in Order.

Remember, the guidance on aggregate is "unit for data changes". Not unit for reporting. If you find yourself loading in a bunch of unnecessary data, maybe the problem is not the pattern, but instead that there are finer grained aggregate boundaries that you should be using.

See Mauro Servienti 2019: all our aggregates are wrong.

3
  • 1
    I would add one rebuttal to your first section. An Aggregate can be partially loaded, as long as the outside world (all code not part of the classes in the Aggregate itself) cannot tell the difference. And that can mean that you might need to change from partially loading to fully loading when the requirements on the Aggregate change. May 27, 2021 at 7:36
  • Bart, that is only true if there’s not a single use case that requires the Aggregate to be fully loaded, in which case the boundaries of the Aggregate itself might be wrong.
    – Rik D
    May 27, 2021 at 7:49
  • @RikD: I think what Bart is getting at is that deferred execution does not inherently invalidate an aggregate pattern, provided that the deferred execution is automatic and implicit, requiring no knowledge/effort from the outside consumer of the domain. This may make little sense in a web context (where the response directly serializes the aggregate) but it can make a significant difference in an in-memory application (e.g. WPF app)
    – Flater
    May 27, 2021 at 9:41
2

The question is: when updating an order item are there any business invariants that need to be checked across the whole order? Or in other words, do you need to take into account all the other order item on the same order when updating a single order item? For instance, let's say an order item contains (amongst other stuff) the product information (e.g. at least product id) and an amount. Now let's say you have a business rule that non-commercial orders must not exceed the total amount of 50 units in an order.

If this were the case you would have to take into account all the other order items as well, even if you just change a single order item in this use case. The business rule of not allowing more than 50 units summed up across all order items has to be adhered inside the transactional boundaries of the order aggregate in this case. Loading all the order items makes this rule easier to implement. And then you can still ask yourself, if I already have this current limitation (there cannot be more than 50 order items), will you expect performance issues? If there are, you can think of making order item an aggregate on it's own and hold only a list of order item value objects with just the relevant information for the order aggregate. Changing the order item would then still only be a single transaction but triggered again through the order. Domain events are one option to implement such an approach but not mandatory.

I think that Vaughn Vernon's sample implementation of an agile project management application might also illustrate an interesting option to deal with such situations. Take a look at the Product aggregate implementation, specifically the method planBacklogItem(). You can consider Product and BacklogItem as somehow similar to an Order and OrderItem relation.

If you expect lot's of order items on a single order and performance is definitely an issue or if updating an order item can happen completely independently as it's own transaction, having order item as separate aggregate sounds like a good idea.

Another option, if you still want to make order item an entity of an order and updating a single order item does not need to take into account all the other order items, lazy loading might be an option for you. You look at Vladimir Khorikovs blog on that topic.

It really depends on the domain model and business logic requirements. I would usually think of what business invariants need to be adhered first and trying to optimize for performance as second priority if I cannot even think of any performance issues with my current use cases and domain logic constraints.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.