Domain-Driven Design – How to Avoid Overlapping Aggregate Boundaries

aggregatedomain-driven-designdomain-model

I recently started reading Evan's book about DDD, and I decided to try and apply some of the principles from that book on a bounded context of a project I've been working on.

The context of interest deals with appointment and appointment scheduling.
I will present some of the business requirements, as well as the current model.

We have Employees, Appointments and Rooms. An appointment can have at most 1 employee per role, for example 1 counselor, 1 doctor. These employees all start and finish their part of the appointment at a different time. For example, a counselor can be present from x1 till y1, and a doctor can be present from x2 till y2 (these can overlap, or be disjointed). The appointment is considered to start at the earliest x, and end at the latest y. An appointment can also have only 1 patient. An appointment must take place in a room. Employees cannot have overlapping appointments (regarding to their own start and finish times). An appointment cannot take place in a room if that room is occupied for a different appointment.

This is the model I came up with:

initial model

In addition to the previous invariants, these business rules must be upheld: A patient can have only 1 scheduled appointment at a given time (assume there is a status attribute in Appointment).

The model should support the following use-cases:

  • Scheduling an appointment.
  • Updating an appointment schedule's start/end time
  • Assigning a different employee to an appointment
  • Adding/removing appointment schedules from an appointment

I have trouble defining the aggregates, their roots and boundaries. From the given constraints, I think it is pretty obvious that the DailySchedule aggregate needs to encompass the Availability and AppointmentSchedule value objects. However, Appointment also needs to encompass AppointmentSchedule in order to maintain the invariant that there should be only 1 employee role per appointment. Furthermore, Room and Patient both need to encompass Appointment in order to maintain their invariants.

Supposing that Patient's invariant can be modeled as a business rule/policy object, that still leaves DailySchedule and Appointment sharing a reference to a value object, without going through its aggregate root.

It seems to me that this model does not allow for clearly defined aggregate boundaries. I was considering exposing SERVICES for each of the use-cases, that would span between the aggregate boundaries and maintaining the invariants from there. I am interested to know if there is any other, possibly more elegant/simple solution to this problem.

Best Answer

A good starting point for you would be Yves Reynhout's Evolving a Model, where he describes the modeling of appointment scheduling for healthcare.

You might also want to review Greg Young's discussion of warehouse systems. The summary is this: if you have a reasonable way to detect and report a scheduling conflict in your data, you may not necessarily need to prevent violations from occurring.

One insight that helped me to grasp this is that the order that messages arrive is not a guarantee of correctness. Udi Dahan, in Race Conditions Don't Exist, observes

A microsecond difference in timing shouldn’t make a difference to core business behaviors.

Here's a message saying that Dr. Frankenstein should be in room A from 10:00 until 11:00; here's another message saying that she should be in room B from 10:30 until 11:30. It's clear that the two messages are not consistent, but we don't know which is right - in particular, there might already be a message coming that cancels the "wrong" appointment, we just haven't seen it yet.

In many cases, what we are really looking for is something like the solutions Greg Young describes in Stop Over Engineering; first escalate problems to the human beings, then work on identifying problems that are simple enough that solutions can be automated.

Systems that overly constrain to prevent conflicts often inflict disappointing experiences on the users. There was a data entry error, but before we can enter the correct data for this appointment, the system interrupts that tasks and insists that the error be corrected. Furthermore, correcting that error may be complicated.

If you keep in mind that the role of a scheduling system is to identify and resolve conflicts, rather than prevent them, you may find that you have a lot more freedom in how you design your aggregates.

Related Topic