Design to avoid holding references to child entities

1,995 views
Skip to first unread message

Michael Bergman

unread,
May 28, 2015, 8:16:06 AM5/28/15
to ddd...@googlegroups.com
What are the reasons for not being allowed to hold references to a child of an aggregate? I’m guessing one reason is that an aggregate root must be able to delete all of its children if it wants to. Are there any other reasons behind this principle?

Do anyone have an example when you needed to violated this principle?

In our case I have a couple of places in the current design where this principle is violated. For example we have an aggregate root “SalesOrder (Entity)” having children of “Orderline (Entity)”. We also have an aggregate root “Shipment (Entity)”. The things that are shipped are order lines. In our design Shipment holds a reference to "Orderline" to know what has been shipped. How could this be re-designed?

Thank you any tip that can help me better understand this principle and align our design to it.

Michael

@yreynhout

unread,
May 28, 2015, 8:38:45 AM5/28/15
to ddd...@googlegroups.com
Bounded contexts?

João Bragança

unread,
May 28, 2015, 8:49:16 AM5/28/15
to ddd...@googlegroups.com
An aggregate may hold references to child entities. It should not hold a reference to a different aggregate.

HashSet<
  Tuple<
    Guid, // orderId
    int // orderlineId within the order
  >>

Why should deleting an order (whatever that means) also delete the lines within the shipment? You probably do not have a fleet of predator drones ready to strike the UPS truck when someone deletes an order (and even that would be eventually consistent). You're just gonna have to ask the domain experts what happens to a Pending Shipment when someone Cancels an Order, you have insufficient stock, the quantities on the order change, etc.

--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Michael Bergman

unread,
May 28, 2015, 9:01:15 AM5/28/15
to ddd...@googlegroups.com
Currently we only have one bounded context (the core – “Sales Bounded Context”).
We are considering in the future to break out “Shipment” to a different bounded context.
Even if we do that I imagine that the principle remains the same and Shipment may never hold a reference to an “Orderline”.

Greg Young

unread,
May 28, 2015, 9:07:42 AM5/28/15
to ddd...@googlegroups.com
Shipment and Order could have different validation logic

What happens if I change an order line via an order but it would make
it invalid from the shipments perspective. This is the main reason why
you don't point to objects in other aggregates.

As for one aggregate pointing to another. There is a teaching rule to
ideally not hold hard references but to use soft references instead.
The problem with hard references is that people use them. This is not
a hard fast rule though and is mostly there to help people break the
"one giant graph" mind set.
> --
> You received this message because you are subscribed to the Google Groups
> "DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to dddcqrs+u...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Studying for the Turing test

Michael Bergman

unread,
May 28, 2015, 9:12:40 AM5/28/15
to ddd...@googlegroups.com, joao...@braganca.name

In our case Shipment holds a reference to a different aggregates child which is not allowed. We should therefore remove it.

The predator drones is an appealing solution though. We should start develop these also just in case. :-) Anyway, until then, yes, the Shipment object must remain regardless of changes in “SalesOrder”.

But how should I now be able to query “What have been delivered”? We need to know which order lines have been delivered.

Thank you,
Michael

@yreynhout

unread,
May 28, 2015, 9:38:23 AM5/28/15
to ddd...@googlegroups.com, joao...@braganca.name
When does a sales order become shippable? Why not copy the order lines upon it becoming shippable?

Also there's a difference between reusing an entity "class" and reusing an entity "object". Hope there's no confusion around that.

Michael Bergman

unread,
May 28, 2015, 10:41:51 AM5/28/15
to ddd...@googlegroups.com
Thank you for your input. I think I understand your point. In this case the reference would still exist but it would not be valid or meaningful.
At the same time, I imagine this could also be the case even for references between aggregates even though they are allowed.
By soft references, you mean something like a clear text description of the association? Only intended for information and never used for lookup? Clearly, I see the benefit of this as the graphs can be smaller. At the same time I personally have difficulties to get away from hard references when trying to find a working design.

Greg Young

unread,
May 28, 2015, 10:43:26 AM5/28/15
to ddd...@googlegroups.com
"By soft references, you mean something like a clear text description
of the association?"

An ID.

I can fetch the other one if I need to but they don't have actual
references (e.g. object references)

Michael Bergman

unread,
May 28, 2015, 10:59:22 AM5/28/15
to ddd...@googlegroups.com
Now I understand and yes I agree. We do follow that teaching as we only have hard references (navigable) within an aggregate root. When referencing another aggregate we use soft references (i.e. ID). As I understand it you should not hold a reference (soft or hard) to another aggregates child.

Michael Bergman

unread,
May 28, 2015, 11:46:18 AM5/28/15
to ddd...@googlegroups.com, joao...@braganca.name
Good suggestion! Copying the order lines into e.g. an entity “PlannedShipment” could be an option. I never thought of that. During the copy the local identity of the order lines could also be removed.

The order lines become shippable as soon as they are in stock. Currently “Orderline” represents what needs to be delivered while “Shipment” represents what has been delivered. Nevertheless, late changes are allowed within the “SalesOrder”. E.g. the quantity of an already shipped order line could be increased. I’m afraid this will make it difficult to keep “PlannedShipment” eventually consistent as local identities of “Orderline” must not be exposed.

I’m sorry but I am actually confused regarding the difference between reusing an entity object (referencing it?) and reusing an entity class. By the latter, do you mean that e.g. “Orderline” could be a different entitty or value object in an intended “Shipping Bounded Context”? Anyway, I do at least know the difference between a class and object :-) I’m grateful if you could elaborate.

Ben Kloosterman

unread,
May 28, 2015, 8:41:00 PM5/28/15
to ddd...@googlegroups.com
When changing cancel the delivery and create a new one . 

Regarding entity in DDD at least  , all methods are done for the key entity the aggregate root ( often the root entity) . ie Orderline and even item would not have methods  ( and yes line items are normally value objects but even if an entity it would not have methods) .  This is very important . Also multiple aggregate roots may use the same entity / value object but use different methods.  Basically entities and value objects are often / most of the time represented as  naked objects. Its the aggregate root design that is the key .. multiple aggregate roots form the context. Note the aggregate roots each form a consistent tree. 

An example here is    order is an aggregate root and entity , it has a collection of value objects line items which in turn have an item name , weight , price  and item code ( id) there is no need normally to have a separate Item object unless you have many customization  . Order has all the methods for working with order and this entire structure can be saved as a single blob ( It doesn't have to be  item could be a different table but the blob illustrates its a unified structure ). There is likely another aggregate  called Item which deals with creating / displaying items and a list of attributes colour , shape , type etc  but these have nothing to do with Order . For handling stock the order process may  run the order code on order and then  make a call on the Stock aggregateroot to tell it the itemIds and qty - note it doesnt send references) . Hope that made sense. 


Its more of a mindset change for years we have thought of normalized data structures because  of the physical limits of disks since we don't want a db that grinds to a halt when you scale . These days storage has changed and the old pattern becomes a limit and reading larger blocks can be quicker than fetching multiple data especially cross machine  . So copying / reading larger amounts  of information is now a strong option which can then be used for simpler more loosely coupled and more accurate modelling.

Ben

Michael Bergman

unread,
May 29, 2015, 9:28:28 AM5/29/15
to ddd...@googlegroups.com
Interesting, I didn't know that you are supposed to have methods only in the aggregate root, but I'm also quite new to DDD. Is there any chapters you recommend in one of the DDD books that explore this concept further and the rationale behind it? I’ve always tried to put logic down into the lowest child entity. E.g. “Orderline” has a method to calculate a price from unit price and quantity. Why would I put this responsibility in SalesOrder? I'm probably missing the point.

The other aspects, however, I recognize and it makes sense. And since Item in your example is an aggregate root it is always OK to pass an ItemID around.

I’m going to try to re-design my example according to the suggestions I’ve received. I’ll post a reply if I get stuck. Thanks!

Michael

Thomas Presthus

unread,
May 30, 2015, 6:30:20 AM5/30/15
to ddd...@googlegroups.com
Child entities can, and most often should, have methods. The "Tell, don't ask"-principle applies here as well. The important thing is that the methods of child entities are not used from outside the aggregate. 

Thomas.

pd

unread,
May 30, 2015, 12:01:15 PM5/30/15
to ddd...@googlegroups.com
Very interesting and great explanations Bennie.

"[..] and the rationale behind it? I’ve always tried to put logic down into the lowest child entity. E.g. “Orderline” has a method to calculate a price from unit price and quantity. Why would I put this responsibility in SalesOrder? I'm probably missing the point."
This is exactly what I was wondering/thinking.

I use methods in child entities too and wasn't aware of this mentioned practice.
Thomas here above seems to have other opinion though (with the classic rationale behind, the one I've based my assumptions on too).

Apart from that question, this other thing about DDD and aggregates, where child entities _classes_ are shared between aggregates, I find that very interesting too and have actually failed to implement it so far.
If we were to assume that they lack behavior, they are in essence contracts, DTO's.

Greg Young

unread,
May 30, 2015, 2:04:58 PM5/30/15
to ddd...@googlegroups.com
"Apart from that question, this other thing about DDD and aggregates,
where child entities _classes_ are shared between aggregates, I find
that very interesting too and have actually failed to implement it so
far.
If we were to assume that they lack behavior, they are in essence
contracts, DTO's."

Instance should not be shared classes are perfectly fine to share
(even with behaviour)
> --
> You received this message because you are subscribed to the Google Groups
> "DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to dddcqrs+u...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--

Ben Kloosterman

unread,
May 31, 2015, 10:04:01 AM5/31/15
to ddd...@googlegroups.com
i think child behaviour is ok when not shared but what i found is they often prevent sharing or end up becoming linked aggregate roots when shared eg its hard to manage item in sales order and item in the item aggregate root  . Many such designs will devolve to crud /traditional oo unless your experienced. So i suggest for small aggregate roots and people who are new to use no or only trivial methods on shared entities though i have done more cqrs than ddd.

Ben

On Sun, May 31, 2015 at 2:01 AM, pd <pyd...@gmail.com> wrote:
Very interesting and great explanations Bennie.

"[..] and the rationale behind it? I’ve always tried to put logic down into the lowest child entity. E.g. “Orderline” has a method to calculate a price from unit price and quantity. Why would I put this responsibility in SalesOrder? I'm probably missing the point."
This is exactly what I was wondering/thinking.

Ok when isolated ,but what happens when you want to share it ? Then it becomes a  variant of  the fragile base class problem. Put it another way for non large roots what is the harm ? The ar provides the encapsulation.

Michael Bergman

unread,
Jun 1, 2015, 5:22:53 AM6/1/15
to ddd...@googlegroups.com
Thanks Thomas, Greg and Bennie for the explanation. I realize the potential problems if you have shared entity classes in your design.

Michael Bergman

unread,
Jun 1, 2015, 5:39:19 AM6/1/15
to ddd...@googlegroups.com
I have re-designed the delivery problem. I’ll be happy for any feedback – is it better or worse? :-) Perhaps hard do answer without all the requirements but at least if it is valid according to common DDD principles.

First design: http://yuml.me/ed4306a8 . Entity “Fulfilment” is red because it links to a child entity.

Re-design: http://yuml.me/0144304f . The main change was to break out “Allotment” from the “SalesOrder” aggregate as suggested. I have also added a property “ReferenceNumber” so that the client that creates “Allotment” can find it later. I’m thinking in this case that it could be OK to send in OrderlineId”. Alternatively, the client could save a list of AllotmentId.

As always, the real problem was a little bit more complex than first explained. In the design you will find a class named “Allotment” because one order line may be requested to be sent to zero, one or many addresses. Also, there are three scenarios for “Shipment” that is handled by a “Fulfillment” class:
- One-to-one: One allotment is fulfilled completely by one shipment.
- One-to-many: One allotment is fulfilled by several shipments. E.g. an Allotment of 15 units may be shipped with 10 units first and later the remaining 5 units.
- Many-to-one: One allotment can be shipped in a shared shipment. E.g. there may be many Allotment (even from different SalesOrder) going to the same address. To save money, these are sent as one shipment.

As some have pointed out, Value Objects could be better in some places. I have not focused on that in this re-design.

Michael

Alex Farcas

unread,
Jul 14, 2015, 4:54:07 PM7/14/15
to ddd...@googlegroups.com
I have a very similar problem except that in my case an Order can be split into multiple Shipments. Additionally, an OrderLine can be part of multiple Shipments. There are situations when the client orders N items of the same Product (represented as a single OrderLine) and in order to optimize shipping costs, those N items are split into M Shipments. Not all those M Shipments might be created at the same time (some several days apart, depending on inventory availability). 

Given the above, the OrderLine needs to hold some counters with the number of items (initial_qty, part_of_shipment_qty) and it also has a status ("unfulfilled", "partially fulfilled" and "fulfilled"). Although i would have preferred to make OrderLine a VO, all this forces me to make it an Entity since it has a lifecycle. ShipmentLines on the other hand can be plain VOs and in my implementation they just copy data from the OrderLines they represent (although they don't have the exact same fields).

A Shipment goes through several status changes and mutations before being actually shipped. So now let's assume that a Shipment needs to be cancelled (prior to being actually shipped, after which cancellation is no longer be possible). What happens to OrderLines that shipment was made for? I need to be able to modify the part_of_shipment_qty and possibly the status too. How do i know which OrderLine a ShipmentLine refers to if i do not maintain some sort of id within the ShipmentLine? The only way it could do that was if OrderLine was an agg root itself but that doesn't seem right (an OrderLine does not make sense without an Order, so the Order must be the agg root). 

I'm pretty sure some of my assumptions or modeling decisions are wrong. Can you please shed some light on my confusion?

Kijana Woodard

unread,
Jul 14, 2015, 8:18:27 PM7/14/15
to ddd...@googlegroups.com
I'm a bit short on time at the moment, but you're better off starting your own thread and referencing this one.
I almost missed the question portion of your post as I thought you were just answering the OP.

Reply all
Reply to author
Forward
0 new messages