9

I'm stuck on finding the proper way to refer to entities located inside an aggregate root, when we only got their identities coming from URL parameters. I asked a previous question which ended up focused on value objects, so I'm starting with another example here.

Let's say we want to modify an OrderLine inside an Order:

  • The user goes to a page where he can see the Order summary along with all its Order Lines.
  • The user clicks on the edit button next to an Order Line.
  • He gets directed to edit-order-line?orderId=x&orderLineId=y

Now if I need to update the quantity in the OrderLine, I can do:

Order order = orderRepository.find(orderId);
order.updateQuantity(orderLineId, 2);

However, I don't feel very comfortable with the idea of leaving the responsibility to the Order to retrieve parts of itself by Id. My view on the subject is that within the domain, we should just talk with objects, and never with Ids. Ids are not part of the ubiquitous language and I believe they should live outside of the domain, for example in the Controller.

I would feel more confident with something like:

Order order = orderRepository.find(orderId);
OrderLine orderLine = em.find(OrderLine.class, orderLineId);
order.updateQuantity(orderLine, 2);

Though I don't like the idea of interacting directly with an Entity Manager, either. I feel like I'm bypassing the Repository and Aggregate Root responsibilities (because I could, potentially, interact with the OrderLine directly).

How do you work around that?

Community
  • 1
  • 1
BenMorel
  • 34,448
  • 50
  • 182
  • 322

3 Answers3

13

In my opinion there is nothing wrong with this approach:

Order order = orderRepository.find(orderId);
order.updateQuantity(orderLineId, 2);

orderLineId is a 'local identity'. It is specific to aggregate root and does not make sense outside of it. You don't have to call it an 'id', it can be 'order line number'. From Eric Evan's book:

ENTITIES inside the boundary have local identity, unique only within the AGGREGATE.

...only AGGREGATE roots can be obtained directly with database queries. All other objects must be found by traversal of associations.

Dmitry
  • 17,078
  • 2
  • 44
  • 70
  • I agree - orderLineId doesn't have to mean your DB id, it may be anything that uniquely identifies order within this single Aggregate (seq. number of the line or aggregated code in form of orderNo/LineNo). It's responsibility of Order to find particular line by this identifier. – kstaruch Sep 05 '11 at 17:25
  • Wouldn't this approach bloat the interface of the AR, if the AR even had just 2 entities with 2 methods each, the AR would need to have 4 methods to support the entities, Is this the ideal approach ? – Sudarshan May 20 '13 at 17:51
  • It depends, and one can argue that you already having a problem when you have such a large aggregate. This might just be a sign that you need to reconsider your design toward a smaller aggregate, see: http://dddcommunity.org/library/vernon_2011/ – Dmitry May 20 '13 at 18:04
1

OrderLineId is what exactly? It has no meaning. You're updating the quantity of a PRODUCT and that's what should be used as the id.

Order order = orderRepository.find(orderID);
order.updateQuantity(productID, 2);
nilskp
  • 3,097
  • 1
  • 30
  • 34
  • 1
    That's a relevant objection, although in our application, you may have several times the same Product in your shopping cart, with different Options selected (think about a green tee-shirt, and a red tee-shirt; if I want to update specifically the quantity for the green tee-shirt, I don't want to pass the full array of options to match the correct order line: color, size, ... and therefore passing the `orderLineId` is what makes the most sense to me at this point). – BenMorel Nov 21 '11 at 09:31
  • 1
    Fair enough. Although I would probably still opt for a more natural key, such as productID + whatever else makes it unique. But of course, the OrderLineId will still work. – nilskp Dec 05 '11 at 21:47
0

Aggregate Roots are bound to context, in your Context the Order is the AR so it is OK to update it directly since you are exposing it directly, if that code affects other entities they should live in the Order AR.

If you want a more purist approach you either have to make a findByOrderId in the AR and load it entirely or expose the OrderLine and OrderId in your application (then using your second approach).

Francisco Aquino
  • 9,097
  • 1
  • 31
  • 37
  • 1
    I think Benjamin's question is about the best way to navigate from aggregate root to the orderline within it that he wants to update. Having the application code iterate through the orderlines seems to cross the line for application code. – Eric Farr Sep 05 '11 at 17:13
  • Thanks for the clarification, I confess I do not go by the 'unique only within the aggregate' Dmitry posted above, wondering now if I ever came across such implementation. – Francisco Aquino Sep 06 '11 at 23:08