Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD - Enforcing rules which need to know about multiple aggregate roots

I'm new to DDD, and currently looking at rebuilding an existing application by starting with a bit of a proof of concept while I'm still finding my way with DDD. My questions here only concern a small part of the domain model, so it may seem overly simplistic at the minute.

It's a scheduling application for nurses who visit patients in their homes. As such, it's clear that "Patient" is one AggregateRoot, and "Nurse" is another AggregateRoot. There is no direct tie for a patient to a nurse, aside from when a nurse is assigned to visit a patient using an "Appointment" entity.

Now, the appointment entity could easily belong to either patient or nurse AR's, or even both seeing as an appointment is a link between the two. As such, I'm also making the Appointment into an AR. So the first question is:

1) Does this modelling sound right? I was originally trying to add a collection of Appointment entities under either the patient/nurse AR's, but as it really belongs to both, it makes sense to be it's own AR. I was then thinking of adding a list of appointment ID's under the Nurse / Patient AR's to link to their appointments, but this would mean a transaction to save an appointment would need to affect multiple AR's at once, which from what I can tell would suggest a bad aggregate design.

Assuming this modelling makes sense so far, I now need to work out the best way of enforcing business rules which concern all 3 of the current AR's. For example, a nurse can't be in more than one place at once, so we can't create an appointment at the same time as another one assigned to the same nurse. It's also only possible to have a single pending appointment per patient. So the second question is:

2) How would you go about enforcing these kinds of rules, which concern multiple different AR's? Obviously the rules would be easy to enforce, and very self contained if the appointments were a nested collection under either the patient or nurse AR. This is now making me question whether my modelling is correct or not.

I've read a lot around BC's and Saga's / Process Managers, but to me this is all part of the same BC so not sure I need anything that complicated. Is it acceptable to simply have a CommandHandler which loads up multiple AR objects and uses their state to determine whether an appointment can be created or not?

If so, and tying back in with Q1 above (assuming I don't store a list of appointment ID's under the nurse / patient AR's), the read model is the only way of easily finding the appointments belonging to the respective nurse/patient - so is it also acceptable to enforce business rules based on the state of the read model rather than an AR from the repository?

Hope this makes sense, and thanks in advance!

like image 259
Stu Ratcliffe Avatar asked Oct 15 '18 08:10

Stu Ratcliffe


2 Answers

Does this modelling sound right?

No (but that's not your fault -- the literature sucks). Your aggregates are going to be representations of information, not people who move around in the real world. Rotation Schedules, Duty Rosters, those are the kinds of things that may be aggregates.

For example:

a nurse can't be in more than one place at once, so we can't create an appointment at the same time as another one assigned to the same nurse

That's not a constraint on the nurse, that's a constraint on the schedule.

"At 9am, Nurse(id:12345) is to visit Patient(id:67890)" is a schedule entry. It's perfectly straight forward to manage all of the schedule entries together. Views of the schedule may also need to include additional information about the Nurse or the Patient, so the view may join additional information.

The schedule becomes its own "aggregate", using correlation ids to enable joins with other information.

Would the schedule be a "NurseSchedule" or a system-wide "Schedule"?

This is probably something specific to the use case of scheduling nurses. Depending on the domain, a given schedule might span a number of nurses and patients.

like image 151
VoiceOfUnreason Avatar answered Oct 09 '22 12:10

VoiceOfUnreason


Is it acceptable to simply have a CommandHandler which loads up multiple AR objects and uses their state to determine whether an appointment can be created or not?

No, not if you want to follow the DDD approach. The Aggregate should not be smaller than a transaction, the Aggregate is the transactional boundary.

From what I see you have these business invariants:

  1. a nurse can't be in more than one place at once, so we can't create an appointment at the same time as another one assigned to the same nurse

  2. it's only possible to have a single pending appointment per patient

These two rules can be strongly enforced only if the Nurse and the Patient belong to the same Aggregate. That is, you should have a big Aggregate if you want the two rules to be respected no matter what.

But having such a big Aggregate may feel wrong. There is something that you can do: a trade off: which of the two rules can be enforced in an eventually consistent manner? After you discuss with the business specialists and present him/hers the business implications, you pick one and then you create a Saga/Process manager that detects such an invalid state and corrects it and/or notifies someone to manually correct it.

If so, and tying back in with Q1 above (assuming I don't store a list of appointment ID's under the nurse / patient AR's), the read model is the only way of easily finding the appointments belonging to the respective nurse/patient - so is it also acceptable to enforce business rules based on the state of the read model rather than an AR from the repository?

A Saga/Process manager uses old data (eventually consistent updated data) to send the right commands to the Aggregates, just like the Readmodels. So, you could have the Saga maintaining a private state (a safer/cleaner solution) or let is query a canonical Readmodel to find the invalid cases (a quicker/dirtier solution).

like image 44
Constantin Galbenu Avatar answered Oct 09 '22 12:10

Constantin Galbenu