Reputation: 13531
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:
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.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...
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.
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?
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
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
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