In the real world we can be vague about our business rules and processes. We can make exceptions or we can bypass a few steps to accommodate special circumstances that we never anticipated.
Imagine a business rule that all customers must have a first name, middle name, and last name. If someone arrives in a physical store without a middle name or even without a last name, whatever name they do have can be written down.
In software, reacting to unforeseen circumstances in real-time is not possible. We have to specify with great precision in code exactly what our business rules are, and the computer will apply them exactly as we tell it to.
When converting fuzzy real-world business rules into precise computer code there is the potential for a whole range of problems to arise, significantly impacting customer experience, revenue generation, and even company reputation.
If someone from a different culture with a different naming convention cannot be served by a system, it’s not good business or publicity.
Check out Gojko Adzic’s talk People That Make Computers Go Crazy for many other real examples. I actually saw Gojko’s talk live at NDC OSLO in 2018. He had the whole crowd laughing out loud for an hour yet delivered an incredibly serious message. Watch it if you get the chance.
The gap between defining business requirements and translating them into software needs to be minimised in order to prevent this category of problems. This is where organisations should adopt a domain-driven mindset. Technical experts and business experts collaboratively domain modeling to identify real business rules.
Domain-Driven Design’s Aggregate pattern is used to bridge the gap. It acts as a collaboration tool to balance the need for business correctness, user experience, and technical efficiency.
The healthcare example used in this article is based on a discussion I recently had with Kacper Gunia based on the needs of a real client. It’s not a fake example and it is typical of the challenges we face when working with our clients.
Transactional & Invariant Business Rules
To enforce business rules in software systems, transactional boundaries need to be identified: which business rules must succeed or fail together. This has significant implications at both a business and technical level.
For example, a user must specify their first, middle, and last name before an account is created. They can’t create an account without all three names. When a user enters their details, their request to create the account — the transaction — will be rejected if they don’t meet all of the criteria.
The business rule is that a customer should never under any circumstances exist in the system without a first, middle and last name. Let’s refer to this as an Invariant Business Rule.
Defining Aggregate Boundaries
Deciding that specific business operations should succeed or fail together sounds like a simple requirements gathering exercise. As you gain experience in your career, you realise it’s not.
Consider the scenario of an appointment scheduling system for a healthcare practice. In each day, there are 60 slots each with a 10 minute duration. In the software system, we need to decide the business transaction boundaries aka DDD Aggregates.
The developers decide that a single 10 minute Slot is their Aggregate boundary. They choose this because it is smaller and should be faster to load and save in the database.
Everything seems to be fine until the developers start receiving complaints that some patients are booking multiple appointments on the same day, and the business rule is that this should not be allowed.
The Limitations of Database Transactions
The developers try to add a rule to the code that whenever a patient requests an appointment, if they have an existing appointment already on the same day the second appointment will be rejected.
But there is a problem — database transactions. If a patient tries to book two appointments at the same time, the rule can be bypassed and both bookings will be made. How is this even possible after we added the business rule preventing this?
It is possible for the patient to book multiple appointments because the database transactions do not align with the business transactions. Because the Aggregate boundary is a Slot two transactions are needed to update the two Slots.
If the second transaction starts after the first starts but before the first completes it won’t know that the first appointment has been scheduled and it will save the second.
Increasing Aggregate Size to Enforce Business Correctness
You’ve probably noticed that there is a simple solution here — to make the aggregate bigger. Instead of a Slot, the Aggregate could be a Day Schedule — all of the slots in a single day.
If the patient now tries to book 2 appointments on the same day, the database transaction will prevent the second appointment from being scheduled. Do make an effort to understand how this works if it’s not clear yet…
The first transaction will begin for the first appointment, then the second transaction will begin for the second appointment. The first transaction will commit and save the first appointment. But when the second transaction tries to save the second appointment it will fail.
The second transaction will fail because the database can see that changes were made to the aggregate inside first transaction and it might not be safe to commit to the second transaction without overwriting data from the first transaction. This is known as optimistic concurrency.
It seems like the problem is solved. A patient cannot schedule two appointments in the same day.
Concurrency Conflicts Harming User Experience
By increasing the size of the aggregate the risk of unintended side-effects also increases. The following examples demonstrates this.
A new Invariant Business Rule is introduced stating that if a patient’s appointment is cancelled by the practice they should automatically be scheduled another one ASAP. No patient should have an appointment cancelled without an alternative being scheduled.
The current aggregate wont allow this invariant to be enforced. The invariant spans multiple days but the aggregate boundary is a single day. Simple solution again: the aggregate can now be a Week Schedule or a Month Schedule.
In fact, why not make the aggregate a Year Schedule to ensure no patient can have more than 20 appointments per year to match another business rule?
The answer is performance, but more importantly concurrency conflicts. This isn’t a technical problem, this is a business problem harming your user experience.
If the aggregate is a Week Schedule then two patients trying to make a booking for different slots in the same week could be impacted. The second patient’s request will be rejected because the aggregate was already updated. Making the aggregate bigger makes the problem even worse.
Nobody in the business wants two completely separate patients to be prevented from making bookings on separate days. They don’t understand software, they. just see incompetent developers waffling about tech.
Uncovering Real Business Rules
To a junior or naive developer, aggregate issues seem like technical problems. But the way to solve them is actually by better understanding the domain.
With an understanding of the performance and concurrency implications of enforcing Invariant Business Rules we can ask the business, what would happen if a patient booked two appointments on the same day or a reschedule wasn’t instantly provided?
What if we cancelled the patient’s appointment and it took up-to 10 minutes until a new one was rescheduled?
Will planes start falling out of the sky? Will medical supplies run out? Will the world’s oceans run dry?
Sarcastically challenging requirements in a deadpan British accent is optional but adds to the experience.
From Software Race Conditions to Eventual Business Rules
When faced with the choice of a system that bizarrely annoys users or having a 10 minute delay in rescheduling appointments, the business will probably decide that the invariant is not really an invariant after all and is an Eventual Business Rule instead (sometimes its developers who assume business rules are invariant).
Deciding where to place aggregate boundaries directly determines the type of race conditions that can occur in a system. By exploring aggregate boundaries and gaining clarity on whether business rules are truly Invariant or Eventual is absolutely necessary to find good aggregate boundaries.
If you’re a developer and you’re not exploring aggregate boundaries with domain experts, you’ve probably got sub-optimal boundaries.
The Formula for Choosing Aggregate Boundaries
Choosing aggregate boundaries is fundamentally a simple equation where we trade-off the following criteria:
- Correctness: enforcing business rules which should never be violated
- Concurrency: ensuring users can work in parallel without un-necessarily affecting each other
- Complexity: large complex aggregates or asynchronous processes (often necessary for Eventual Business Rules) can increase maintenance costs and reliability of software applications
- Performance: optimising the responsiveness of the system by not having to load and save large data payloads from the database
Finding the right balance is where the challenge lies, and it is a skill.
Maybe large aggregates are better: the healthcare practice may have a single doctor and only 1 or 2 appointment requests per-hour. Performance and concurrency aren’t a concern. So correctness and simplicity are preferable.
Maybe small aggregates are better: there are 100 healthcare practitioners working each day and a team of call centre staff constantly taking bookings. Performance and concurrency matter so much more that correctness and simplicity need to be compromised.
Maybe somewhere in-between is better: there are 20 healthcare practitioners and a few call centre staff. Having a Year Schedule regularly results in concurrency conflicts so is too big. But having a Day Schedule will require a more complex rescheduling workflow to be developed.
By discussing with the business, you may learn that 99% of all reschedulings affect only a single calendar week. If by defining the Week Schedule as an aggregate performance is still great and there are no concurrency issues it seems like a good choice for the requirements.
Maybe there is a true Invariant Business Rule: do they really exist?
Evolving Your Aggregates Over Time
The initial criteria on which you base your aggregate design is likely to change over time. Accordingly, you must continually assess and challenge your aggregate boundaries.
If the healthcare practice hires more health practitioners and call centre staff, suddenly performance and concurrency issues may become a genuine problem (this really did happen).
In such a scenario, the balance shifts from needing high correctness and low simplicity to needing high performance and concurrency, so the aggregate should be redesigned.
Understand Your Domain to Find Good Aggregates
Hopefully it’s now clear that the DDD concept of an aggregate is not just a technical pattern that software developers geek out about. Aggregates encourage us to probe deeper into the domain and provide a better user experience.
So how do you find a good aggregate?
- Model your domain to uncover potential invariants
- Challenge the invariants with “what if” questions to determine if compensating actions are sufficient
- Analyse any existing data to determine the performance and concurrency impact of your aggregates
- Establish minimum levels of correctness, concurrency, and performance
- Identify a base design(s) which meets minimum requirement levels with minimum complexity levels
- Explore alternative models that add complexity but provide business benefit
- Anticipate future trends
- Create dashboards to visualise performance and concurrency levels
- Never stop probing for deeper domain insights
In order to be in a position do have the deep and meaningful conversations about your domain, Domain-Driven Design provides a suite of techniques to help you achieve this from EventStorming, to Domain Storytelling, and much more.
If you’d like to go through the whole process of modelling domains, shaping the software architecture, and finding aggregates, join my 2 day workshop at DDD EU in February 2020. Hope to see you there.