The key to loose coupling is to understand how the system you are building is going to change, and minimising the cost of those changes. It’s an extremely simple premise, yet we can’t predict the future, so in practice it’s impossibly difficult.
Is there any way it’s possible to predict the future so that our architecture can easily absorb the changes we need to make of it?
In this article, I’m not going to sell you fortune telling but I am going to show an example of how understanding the type of domain you’re working in can greatly increase your chances of anticipating future changes and being able to accommodate them for the least amount of effort or compromise.
One thing I want you to notice is that good architecture is about more than just defining good boundaries. Getting the interactions right between the components of your architecture is equally important for loose coupling and evolvability.
Let’s start by thinking about the concept of a physical library and seeing how characteristics of a library are transferable to other domains, and how you can use those similarities to create a loosely coupled architecture in your domain.
A physical library is a repository of books. A library allows customers to borrow books by checking them out and then checking them back in.
Libraries have a number of policies which govern checking in and checking out books. Customers must sign up, there is a limit on how many books they can check out, each book can only be checked out by a single person, and so on.
The library also has to keep track of information about those books, such as the title, the genres, and also keeping a record of check-ins and check-outs.
Commands vs Events in the Library
When a customer checks out a book from the library, it’s a command. The customer tells the librarian ‘I want this book, please check it out to me’. However polite the words are spoken, effectively it’s a command — the initiator is asking the recipient to do something specific.
Events Add Coupling in the Consumer
Imagine if events instead of commands were used. All of the following domain events involve a customer who requires a book:
- Geography exam confirmed — a student needs text books to prepare for their exam
- Person became interested in ancient Greece — the person wants to read a book about ancient Greece
- New parent started seeking help — the parent wants a book about parenting techniques
There are limitless reasons people need to borrow books from a library, and new reasons can occur at any time. But the library is not coupled to them. The library does not observe these events and automatically check books out to people.
The library is highly decoupled from reasons that people need books. All the library effectively does is check books out and check them back in, and applies certain policies.
Accepting commands means the library is decoupled from reasons for wanting to check out a book. A command implies that the decision lies with the initiator.
Substitute Books With Your Domain’s Inventory
If you abstract the concept of a book to any item that can be checked out and checked in, can only be checked out to a single person, and has other similar types of policy, you should consider modelling this part of your domain as a library context.
In DDD a bounded context is an independent, loosely-coupled architectural component. It could be a microservice or a module within a monolith. A library context is a specific type of bounded context. If that’s confusing, just imagine a library context is a type of microservice.
Library Context Definition
First I’m going to start by defining the library context pattern, and then I’ll show an example architecture which includes a library context.
A library context is a bounded context which keeps track of inventory. Items in the inventory can be temporarily borrowed by other bounded contexts in the system by being checked out and checked back in again.
Rules and policies relating to the inventory can be applied by the library impacting the checking in and out of inventory.
A library context may also store metadata about the items of inventory, and keep an audit of check-in and check-out history of each item.
Library Context Example: Car Leasing
Consider an example car leasing business which allows customers to temporarily lease cars for between 1 and 28 days. The architecture of the software could be sliced up into a Leasing context and a Car Pool context.
Car Pool keeps track and governs the availability of all cars. It knows if they are available or checked out. Leasing is responsible for managing the leasing process and keeping a historial record of all leases.
Car Pool and Leasing are different bounded contexts, owned by different teams with different stakeholders driving their backlogs.
Integrating via Domain Events
These two contexts could be integrated via domain events.
When a customer attempts to lease a car, Leasing would publish a domain event of type Lease Requested and Car Pool would handle the event and respond with a Car Checked Out.
The car would be checked out to Leasing and it would confirm to the customer that their lease is now active and they can come to collect the car.
If the check-out was unsuccessful, a No Matching Car Available event would be published by Car Pool instead.
Event-induced Consumer Coupling
The choice to use events could turn out to be problematic.
Another context in the system, Servicing, also needs to remove cars from the pool so that periodic and unscheduled maintenance can be carried out. It raises an event, Servicing Required, which Car Pool also handles and responds with a Car Checked Out event.
Over time, more reasons for checking out cars are required by other contexts. The Valeting context needs to check out cars so they can be cleaned. The Marketing context needs to check out cars to be used for promotional purposes and the list of reasons continues to grow.
For each of the business reasons to check out a car, Car Pool must know about and handle the event, and deal with the complexities of versioning.
This is a high level of coupling between the contexts and the teams that own them. The team can become a bottleneck for many other teams, and this design choice can be the result of elevated organisational politics.
Inability to Replace with Generic Solution
Another problematic aspect of this architecture is the inability to potentially replace Car Pool with a SaaS solution if one existed because it is tightly coupled to the company’s business processes and many other bounded contexts.
A simple heuristic you can use is: “Could this be purchased as an off-the-shelf or SaaS product at some point in the future?”. If the answer is yes, it should probably not be coupled to your specific business process steps.
From Events to Commands
To remove the coupling, Car Pool could become a library context by refusing to know anything about domain events from other bounded contexts. Instead, it could simply offer a library-like API with the ability to check-out or check-in inventory items using commands (which are uniform for all existing and future consumers of the service).
Commands put the responsibility onto the consumer to decide to check-out and check-in inventory items. Car Pool does not need to know why, it just needs to decide if the item is available to be checked in and out. It is no longer tightly coupled to the other contexts and has little reason for co-change and with them.
Note: A library context may confirm the check-out either with a synchronous response to the command or an asynchronous message after some additional processing has occurred. Either definition matches the intended semantics.
To Library Context or Not to Library Context?
What kind of domain are you working in? Have you used a library context? Do you see the need for a library context? Or do you feel quite strongly the opposite, that preferring an event-based orchestration approach is superior for checking inventory in and out?
If you have opinions or experiences, your comments on this article are more than welcome or you can contact me directly.
If you like the content in this article, and want to learn more about using Domain-Driven Design to create a loosely-coupled architecture and organisational structure, keep an eye out for my upcoming public workshops, or contact me for private consulting and training opportunities.