5

I understand that we should not change children of an aggregate-root directly, but instead they should be performed via methods on the aggregate-root. E.g. order.SetOrderLineQty(product, qty);

But what if the children of the aggregate-root are something abstract? Imagine you have Car aggregate root, which contains a list of IWheel as part of the aggregate. How would you add/change the properties of the wheel via its aggregate-root (who knows nothing about what the concrete type of wheel they might be)?

A more real world example is this: A doctor can create a MedicalRerport (aggregate-root), which contains a list of IMedicalNote (as part of MedicalReport aggregate). IMedicalNote is a base-class/interface, which is subclassed into a few concrete subclasses, e.g. BloodCheckNote, TemperatureNote, MineralConcentrationNote, etc etc.

Each subclass has different properties, and they're all editable. A MedicalReport aggregate may contain one or more of any of these notes. (Each note subclass has a specific user-control for the user to enter/update the details, shown as panels/tabs under the big MedicalReport screen)

My question is, how can I add/edit the properties of these notes strictly via its aggregate-root (MedicalReport)? Since I am not allowed to change these notes properties directly, one ugly option is by exposing all possible note properties on the aggregate root (MedicalReport), i.e.:

report.SetWhiteBloodCellCount(cellCount);
report.SetBloodCheckComment(comment);
report.SetTemperature(bodyPart, temperature);
report.AddMineral(mineral, concentration);

Each of these methods will update (or create new) note items in its internal children collection. There are 2 obvious problems with this:

  1. We have to define upfront all available properties of all possible IMedicalNote subclasses on the aggregate-root. That's not acceptable as the number of subclasses is guaranteed to grow, depends on the type of medical data we want to capture, which is the whole point of the inheritance in the first-place.
  2. There can be multiple instances of the same note-type within the list. This API will fail, since we can't just say report.SetBloodCheckComment(comment) and expect it will update a BloodCheckNote item in the list, because we allow more than one BloodCheckNote items in the list.

I still want to maintain all interactions to these notes via its aggregate-root, since it has to control whether the whole MedicalReport aggregate is valid to be saved, whether the aggregate is not modifiable, coarse-grained optimistic-concurrency check, etc. But how can I do that?

Sheepy
  • 677
  • 9
  • 17

1 Answers1

4

Wonder if you're mis-interpreting the guidance around aggregate roots (Or maybe I did...).

I never read the guidance as saying: "the aggregate must provide proxy methods for every conceivable property of all its aggregated objects". Rather, I think it says: "the aggregate controls the lifecycle, identity and relationships of its aggregated objects".

So it's perfectly valid for a client to ask the aggregate for a (transient) reference to one of its objects and do something with it. I don't have my copy of DDD here to confirm the wording, but that would seem consistent with the DDD summary ebook (p53) which says:

It is possible for the root to pass transient references of internal objects to external ones, with the condition that the external objects do not hold the reference after the operation is finished.

So, in your case clients would ask MedicalReport for instance(s) of IMedicalNote, get back subtypes, operate on them as appropriate and pass back to the root if applicable.

As I say: can't say for sure that's in line with DDD, but common sense says it's a more scalable and flexible solution than trying to reflect every property/method of every subtype in the aggregate root.

hth.

sfinnie
  • 9,854
  • 1
  • 38
  • 44
  • My understanding is that you can retrieve the internal object (IMedicalNote) but only for readonly purpose. You're not supposed to make any modification to it. All modifications have to be made via the root so that it can control the integrity. Also, what do you mean by passing it back to the root? Once we modify the property of the child externally, then that's it, it's changed (by reference), bypassing the root, isnt it? What does "passing back to the root" do exactly? – Sheepy Nov 02 '10 at 11:43
  • Apologies, answer somewhat ambiguous. Root passes back copies of the objects which client then updates and passes back to root. Root then responsible for checking invariants & setting up relationships. Provides a sensible split of responsibility: root knows about relationships and constraints among aggregated objects but doesn't need to know details of all subtypes. Clients can operate on specific subtypes as required. Note Root might need to know some subtype details (e.g. if it must hold exactly one `BloodCheckNote` but could have many `TemperatureNote`s). – sfinnie Nov 03 '10 at 10:44
  • You mean to create a clone mechanism so that the root will always pass back a clone of each child-note when accessed via root's public getter? And then a way to copy these values back to its actual object once the client return these children back to the root. Just wanted to clarify if thats what you meant, because it sounds like quite a bit of work. Cheers – Sheepy Nov 10 '10 at 02:24
  • Note quite. For Value objects, return a clone. Client will have to create another anyway (immutability). For entities, you could pass a reference out to client. You then need to decide when validation gets done: either in methods of entity or in aggregate when passed back. That's what I was trying to get at: put the entity-specific validation in the entity, anything to do with relationships within the aggregate. Keeps the aggregate interface relatively stable even when you add new subtypes. – sfinnie Nov 17 '10 at 12:49
  • If the client modifies directly to the child-entity reference, then pass it back to the AR, it's a bit pointless because the changes have been made (by the client) directly on the actual child entities, isnt it? Consider if you change order-item quantity via AR, the AR can decide if the quantity is allowed (e.g. based on max order amount). But if you let the client change the quantity directly on the OrderItem object, and pass back to the AR, the actual order-item *has* changed already, and at that point, there's nothing much the AR can do about it. Can you expand your take on this? Thanks – Sheepy Nov 24 '10 at 02:35
  • That's the point I'm trying to make (unsuccessfully - sorry). You don't expose methods on the subtypes to change relationships (e.g. changing order items in order). You only expose the subtype-specific properties. To your original question, MedicalReport would have methods for `getNote()`, `setNote()`, `addNote()`, `deleteNote()` etc. which would take/return instances of `IMedicalNote`. They would implement your domain rules around composition of a `MedicalReport` (e.g. any limitations on total numbers of constituent `MedicalNote`s. `MedicalReport` wouldn't however implement (ctd..) – sfinnie Nov 26 '10 at 12:56
  • (..ctd.) any subtype-specifics. So no methods for `setWhiteBloodCellCount()` etc. Instead those would be declared on the subtypes. So e.g. to update while blood cell count, a client would perform two steps: (1) call `MedicalReport.getNote()`, receiving an instance of `BloodCheckNote`; (2) call `BloodCheckNote.setWhiteCellCount(cellCount)`. That keeps the Aggregate root interface stable even if you add new `MedicalNote` subtypes since it's only dealing with structure (via get/set/add/remove) whilst allowing clients to deal with subtype specifics. – sfinnie Nov 26 '10 at 13:04
  • Thanks for the time to explain. What you suggested reflects what I mentioned earlier about having the client mutat aggregate-child properties directly (without passing it through the root). I.e., to change the quantity of an orderItem, you simply call order.GetOrderItem(product).Quantity += 4; Instead of order.AddProduct(product, 4); I believe this violates the purpose and role of aggregate-root to enforce the integrity of an aggregate, doesnt it? (ctd...) – Sheepy Dec 09 '10 at 03:16
  • (..ctd) Like in the case of adding product to the Order, all changes should go through the aggregate-root (Order), so the root can validate/refuse, or recalculate stuff, to maintain the integrity of the whole Order aggregate. In my case of medical-note, when you mutate the properties of a child-note, it should put the whole MedicalReport status back to "pending-review". It may even validate if the mutation is not allowed (because the medical-report is being reviewed). If the client mutate the child property directly, the root has no control to enforce the integrity of the aggregate (ctd..) – Sheepy Dec 09 '10 at 03:17
  • (..ctd) Which is why the golden rule I have always known was that we should never access an aggregate-child from outside the aggregate-boundary, especially to make changes to its values. Changes to aggregate-children should strictly be performed from the root. E.g. order.AddProduct(product, qty), instead of orderItem.Quantity+= qty – Sheepy Dec 09 '10 at 03:22
  • Afraid I'm still not explaining very well. My point was sub-classes would never expose `addX()` or `removeX()` methods - those would all be done through the aggregate root. Instead the subtypes would expose methods for local properties e.g. `setWhiteBloodCellCount()`. That's what I meant by the root handling all relationship management and the subtypes handling subtype-specific properties. – sfinnie Dec 09 '10 at 10:03
  • Sorry if I misintepret you, but isnt note.setWhiteBloodCellCount() analogous to orderItem.setQuantity() in my Order example? Client shouldn't call orderItem.setQuantity(). They should do it via order.AddProduct(prod, qty), or something like that. So the Order aggregate-root can: 1. ensure that the qty is within allowable limit 2. ensure the order isnt processed yet 3. recalculate taxes etc 4. change the status of the Order to "Resubmitted" Similarly in my medical example, setting white-blood count should be done via root so it can ensure the Report isnt processed yet, and to revert its Status – Sheepy Dec 13 '10 at 22:54
  • So even tho the subtypes (of an aggregate-child) may have public properties, but DDD guidance states that only its aggregate-root may access (at least the setter of) that properties. And for a good reason too: in my case, I want to make sure the Report root is in a state that allows modification of any of its notes' contents. Or to revert its approval situation upon note modification. Just like you shouldnt set the orderItem.setQuantity() directly, isnt it? Thanks for your responses. – Sheepy Dec 13 '10 at 23:07
  • Btw, in my real-world case, changing while-blood count should cause the root to recalculate data-confidence rating. This is the purpose I defined an aggregate in the first place, since all changes will be made through the root, I can enforce the integrity of my aggregate easily. But now if I make the client to change the white-blood properties directly via child setter, this will break the integrity defined in my aggregate. Cheers – Sheepy Dec 14 '10 at 05:22
  • As you've now described it I'd agree with putting methods on the Aggregate root. As I (mis)understood before, there was no external dependency on the subtype properties, nor any rules within the aggregate that depended on them. So, for example, white-blood count I understood as simply an attribute of the BloodCheckNote. Since you have rules within the aggregate dependent on it however that wouldn't work. Coming back to your original question though, I would stil be uneasy about the proliferation of methods on the AR. I'd probably want to explore some transaction- or event based... – sfinnie Dec 15 '10 at 22:40
  • ...means to limit the AR interface and allow methods to be called on subtype instances passed back from AR to clients. – sfinnie Dec 15 '10 at 22:41