L-Four
L-Four

Reputation: 13531

DDD: aggregate root needs information from another aggregate root

I'm thinking about the design of a fairly simple problem, but it I would like to hear other solutions to handle it properly.

At his moment I have 2 aggregate roots:

  1. User: holds information about a user like display name, linked accounts, and a profile, which can be a patient profile or a care provider profile. Such patient profile holds information like birth date and gender. I have a UserRepository that takes care of getting and saving users.
  2. Screening: holds information about health, like length and weight measurements, and all kind of calculated information such as 'short term evolution", based on those lengths and weights. I have a ScreeningRepository that takes care of getting and saving users.

So there is a Calculate() method in Screening, and the goal is that when a weight and/or length is added to the screening, the health properties like short term evolution are recalculated immediately, so that the screening is always in a consistent and correct state.

The problem is dat this calculation also needs gender and birth date of the user, which are stored in the patient profile.

So basically, the aggregate root screening has a dependency on the aggregate root user. So I'm wondering how to go about it...

  1. According to DDD, an aggregate root should not reference another aggregate root. And also, if I would make user a property of screening, the ScreeningRepository would also be responsible to dematerialize user as well, which is of course not his task.

  2. If screening has no reference to user, then Calculate() does not have all needed information. So this means I should probably move it to a domain service, which get user and screening as input, and do the calculation. Fine. But then, how can I make sure that when a measurement is added to the screening, the Calculate is triggered?

  3. The other option I was thinking about was to not make screening an aggregate root, and make it just an aggregate with parent user. This also allows me to better validate the screening, because I can access the User as well. It would solve all issues about the calculation, because I would have all information at hand, but this way, the UserRepository would be responsible to also handle screening, and my aggregate root becomes responsible of users and screenings.

At this point the last option seems to be the only one that makes it easy to fix the problem, but I would love to hear any thoughts on things since I might be missing obvious concepts.

Upvotes: 4

Views: 3461

Answers (2)

Roman Weis
Roman Weis

Reputation: 353

Aggregates are the most misunderstood concept in DDD.

User: holds information about a user like display name

It is never about data, it is always about behaviour. Just an advice for practice: in the first iteration make your whole bounded context a single aggregate, everything in single object.

What will happen? First of all you are now able to satisfy every invariant that can exist in the bounded context. Ok, but that is crazy you will say, I can't just load everything in a single object, this will be super slow and no one else can use the object during this time. Correct! Aggregates are only there for one reason: performance optimization! It is up to you to find invariants that never influence each other so that you can split the object in order to improve the performance of the system. How to split the object?

It seems that patient profile might be a good candidate for an aggregate root with at a method Screen() that encapsulates the current Screening logic and SetWeightInKg(w) to modify the weight. Care provider profile could be another aggregate. Account another one. All just holding a UserId for reference.

The goal is to put invariants that belong together in a single aggregate and separate those that do not, all for the sake of performance.

Upvotes: 2

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57194

There is no magic.

If the domain logic for Screening requires gender and birth date, then you are going to need to get copies of those values into the aggregate. That in turn means that either (a) you pass the values in, or (b) you pass in a capability that supports querying the values.

It's often the case that an aggregate will cache a local copy of data that "belongs" to another aggregate. In which case you may need to work through what happens if that cached data needs to be invalidated (ex: what happens if we later discover a data entry error on birthdate?)

Upvotes: 3

Related Questions