When we think about Domain Driven Design, what things do come first in mind? Is it Entities, Value Types, Aggregate Root, or maybe it is Ubiquitous Language; or Repositories and Services? Well, today we are going to discover less known and often missed out concept of Domain Driven Design: its layers. Domain Driven Design layers is less discussed, nevertheless important concept for integrating different domains and system with one another.
Before we dive into the layers, let’s first set the stage for explanation and identify a problem that may be solved with Domain Driven Design Layers . Every domain has its own Ubiquitous Language. Ubiquitous Language is the only way to name “things” within a domain; whether we write code, documentation, emails or verbally communicate within engineering teams or business; we all use the same language. Ubiquitous Language is used everywhere within a domain to ensure unambiguity. Ubiquitous Language works great within single domain, however what happens if we need to integrate systems that belong to different domains? Which Ubiquitous Language should prevail? Can we mix Ubiquitous Languages from different domains?
The short answer is none of the above, so let’s look into the details.
When we need to integrate different domains, we will have to introduce a layer that will not belong to either domain but translate one domain model (Ubiquitous Language and concepts) into another domain model. That is exactly what Translation Layer is for. Without distinct translation layer one domain model will be “leaking” into another and vice versa creating inconsistencies and confusion.
Translation Layer clearly sets boundaries between domains and is solely responsible for the translation logic.
To better understand the ideas behind translation layers, let’s get to specific examples. We are going to use coffee shop as an example for one domain and coffee roastery as an example for the other domain. Coffee shop takes customers’ orders and serves fresh coffee drinks; it orders coffee on demand from a roastery. Coffee Roastery on the other hand, takes coffee orders from coffee shops and roasts coffee to fulfill the orders; it delivers freshly roasted coffee to variety of local coffee shops.
Now let’s see transition layer in action for the example above.
To better understand how entities are translated we are going to look at coffee restocking process from Coffee Shop and Coffee Roasters points of view.
When a coffee shop is running out of coffee it needs to restock the supply for a specific position, for example medium roast high-altitude coffee. In order to do so, the coffee shop must place an order with a roastery, specifying an item or multiple items, quantity and delivery date. See how coffee shop language is different from a roastery: restock position versus place an order for an item. Translation layer is responsible for taking in a coffee shop position restock request and turn it into an order for the coffee roastery. Translation Layer allows coffee shop domain to use its own concept of position and restock and avoid mixing up with coffee roastery model of order and item.
You may have noticed that coffee shop domain and roastery domain use the same noun order to address completely different concepts. In the context of a coffee shop an order is place by a customer for a specific menu item e.g., large latte. On the other hand, an order for a roastery is placed by a coffee shop and means a request to get freshly roasted coffee by specific date, e.g. 100 pounds of high-altitude medium roast arabica.
Without translation layer the domain models of coffee shop and roastery will be all mixed up over time; order noun will have two meanings making code, documentation and conversations ambiguous.
We already saw how translation layer maps a request to refill a coffee shop position to a roastery order; however, what if coffee shop wants to be notified when roastery completes roasting and is ready to deliver? How would a roastery accomplish the task of notifying a coffee shop? Well, so far, we have looked at One Directional Translation Layer, luckily Translation Layer can be bidirectional. Roastery can use Translation Layer to pass the notification back to the coffee shop.
In this case Bidirectional Translation Layer converts roastery message of order being ready for delivery to coffee shop domain language of restock arriving soon.
So far, we have discussed how two well defined domains can be integrated with help of Translation Layer, however having two well defined domains may not be always the case. It might happen that we have to integrate with legacy system that was not build with Domain Driven Design and has no well-defined domain at all.
What if a system we ingrate with is technologically incompatible with modern technology we use to build our domain model so that we are unable to import code from the other system to build clean translation layer? For example, our system is built with C#, however the other system is built with Cobol; we can’t just import Cobol code in C# projects, so which programming language do we use to build translation layer?
What if other system introduces incompatible changes relatively often and we want to protect our domain from any incoming breaking changes? Is thin translation layer enough to guarantee that?
What if the other system is planned to be replaced in the future, however for the time being it is the only available option? How do we protect our domain from breaking when upcoming migration to newer system is inevitable?
To answer the challenges above we may use Anticorruption Layer. You may think of Anticorruption Layer as more elaborate Translation Layer that might in certain cases grow into a complete system on its own. Anticorruption Layer shields our domain from any disturbance from the outside protecting domain’s integrity. It provides flexibility in designing our domain and peace of mind that our design will not be impacted by external factors.
What physical form does Anticorruption Layer take? Well, it depends. It might be a single service or entire service layer, API Gateway, or may be entire system on its own. Anticorruption Layer might take a form of a set of publishers and subscribers in case of asynchronous model. It might even have its own database.
Translation and Anticorruption Layers are important Domain Driven Design concepts. They both are used to integrate domains. Use Translation Layer when there is not much translation needed or there is low risk involved in integrating with other domain. When integration looks more like wavy and bumpy road, consider investing in building full Anticorruption Layer. Remember, building layers in not a free exercise; it takes time and effort, however clean domain separation pays off in medium and long term. Well defined domain models are easy to evolve, refactor and maintain.
Thanks for your time and hope you found the post useful!