Fusion of Bounded Realms
After Eric Evans initially presented and formulated Domain Driven Design (DDD) in his 2003 book “Domain-Driven Design: Tackling Complexity in the Heart of Software” it has now come to the fore as a heavily discussed topic.
One of the key triggers is the emergence of microservices architecture and distributed systems, as proper understanding and defining clear boundaries around bounded contexts (a key idea in DDD) are crucial for successful implementation.
After the identification of boundaries, I believe the most challenging part is integrating such bounded contexts in a seamless manner.
After a high-level but sufficient description of bounded contexts in the first part of the article, I will delve deeply into different patterns that can be used as tools for integrating them.
As an entry point, I would like to take Martin Fowler’s definition of Domain Driven Design (DDD) and compile it slightly to elaborate the meaning.
DDD is about designing software based on models of the underlying domain. A model acts as a Ubiquitous Language to help communication between software developers and domain experts. It also acts as the conceptual foundation for the design of the software itself — how it’s broken down into objects or functions. To be effective, a model needs to be unified — that is to be internally consistent so that there are no contradictions within it.¹
As per the above definition, software creation needs to be driven by a model that is well-mapped to the underlying domain. Furthermore, that model should be entirely comprehended by developers and domain experts so that developers can fully absorb knowledge from domain experts and domain experts to assess whether the system is built in the desired manner.
However, the question is when your domain becomes bigger and bigger would it be possible to use models in a unified manner?
The short answer is “No”.
For instance,
- In the Inventory Management context the entity “Product” might refer to a specific item that can be purchased. It could have attributes like name, description, price, and SKU.
- Whereas, in the Order Management context it might refer to an item that a customer has placed in their shopping cart. It may have attributes like quantity, price, and status (e.g., added to cart, removed from cart).
- Further, the entity “Product” refers to an item that a supplier provides in the Supplier Relationship Management context.
Therefore,
total unification of the domain model for a large system will not be feasible or cost-effective.²
What is a Bounded Context?
A Bounded Context is a fundamental concept that defines the scope or boundary within which a domain model operates. It contains its own models, rules, and business logic and encapsulates a specific portion of the business challenge.
Bounded Contexts provide exclusive boundaries around a particular domain, enabling a ubiquitous language to be utilized for describing concepts and processes related to that domain.
Here the ubiquitous language is nothing but a shared and precise language that is understood by all stakeholders, including business experts, developers, and stakeholders which helps to avoid misunderstandings and ensures that everyone is on the same page when discussing concepts within that context.
The Bounded Context concept prevents us from ending up a “Big Ball of Mud,” which metaphorically describes a system that has become disorganized, chaotic, and difficult to manage due to bad design decisions, a lack of clear structure, and the accumulation of technical debt. It assists in complexity management by breaking a huge, complicated system into smaller, more manageable sections.
For instance, if we consider an order processing system as depicted below, it is highly possible that we end up as a “Big Ball of Mud” unless bounded contexts are identified.
In his book “Domain-Driven Design: Tackling Complexity in the Heart of Software” Eric Evans provides a clear and detailed definition of a Bounded Context. He defines it as follows:
A Bounded Context is a specific responsibility enforced by explicit boundaries. It has a clear API, and models and represents a certain area of the business. Within that context, all terms and expressions have specific meanings, and everything outside the context is irrelevant to it.²
Alright. Now let’s try to identify Bounded Contexts for the above scenarios.
For example, Order Management Bounded Context is mainly responsible for handling order creation, allocation, modification, and cancellation. Further, it manages order status transitions (e.g., from “pending” to “shipped”) and addresses any domain-specific rules related to order processing. It is now very convenient to use common terminologies within the bounded context for developers and domain experts to crunch knowledge and come up with design decisions.
How Bounded Contexts Talk with Each Other?
Eric Evans emphasizes that Bounded Contexts should have explicit communication interfaces, and interactions between them should be well-defined and managed.
He also suggests that when Bounded Contexts need to communicate, they should do so through a well-defined interface, using techniques such as APIs or messaging protocols. This ensures that interactions are intentional and controlled, rather than ad-hoc and potentially error-prone.
In this section, I will give a sufficient elaboration on how bounded contexts can be integrated with each other while providing real-world scenarios as examples.
Partnership
This communication pattern is used when bounded contexts are developed by teams that are cooperated. Here the most important requirement is two-way coordination where teams find common ground by working collaboratively.
Well-established collaboration practices, high levels of commitment, and frequent synchronizations between teams are required for successful integration in this manner. From a technical perspective, continuous integration of the changes applied by both teams is needed to further minimize the integration feedback loop.³
As an example scenario, we can consider two bounded contexts Order Management and Inventory Management communicate with each other.
Here the Order Management Bounded Context checks with the Inventory Management Bounded Context to verify if the product is in stock. In return, the Inventory Management Bounded Context responds with the availability status.
However, coordination is required when Product representation is altered.
Real-world example:
Consider a company with an Order Processing bounded context and a Route Planning bounded context. Order Processing context has a model with orders, lines, items, and so on, as shown below. However, the Route Planning context always requires net values of line characteristics since when an order is routed for delivery, line-by-line information is not required.
To do the aforementioned conversion (i.e. aggregating line characteristics to obtain their net values), the teams in charge of both constrained contexts might collaboratively maintain a separate model.
For example, in the top diagram of Figure 6, OrderRouteReqranslator can add up shipment weights. Assume that in a future release, Route Planning will require the order’s net price as well. In this situation, both teams must collaborate to add the required functionality to the OrderRouteReqTranslator.
Shared Kernel
Despite the fact that Bounded Contexts provide explicit boundaries, there may be instances where a specific model of a subdomain is entirely or partially shared among them. This shared subset, referred to as the “Shared Kernel,” contains concepts and definitions that are relevant to both contexts.
in Domain-Driven Design (DDD), a subdomain refers to a specific, well-defined area or aspect of a larger business domain
The Authentication and Authorization model, which is shared across bounded contexts, is a good example of the Shared Kernel communication pattern. It is possible that each bounded context will update its permission structures from time to time to accommodate new design changes. As a result, all parties should ensure that modifications are implemented on their side accordingly.
This is applicable when the cost of duplication is higher than the cost of coordination.³
In other words, the Shared Kernel approach should be used when copying changes introduced to the shared model by other parties is more difficult than communicating shared codebase changes among others.
The following diagram summarises the difference between Partnership and Shared Kernel.
Customer-Supplier
In this communication pattern, one bounded context acts as the supplier while the other consumes services from the former. The supplier is called “upstream” and the consumer is called “downstream”. Here the significant fact is the imbalance of power in the integration. Either the supplier (i.e. upstream) or the consumer(i.e. downstream) team can dictate the integration contract.
Conformist
The customer(downstream) here, as the name states, conforms to the high-power supplier(upstream). The upstream bounded context is most likely similar to Salesforce, which does not adapt to meet downstream bounded contexts. Organizational politics can also compel one to conform to the other.
Real-world example:
Suppose your microservice is going to depend on Salesforce as your CRM where you do not have the option to negotiate on their standard objects. Here you can conform your context to Salesforce standard objects.
Anticorruption Layer
As in the conformist pattern here, the downstream does not want to conform as it is to the upstream service but translates the upstream model to the required form and consumes.
Here the translation is done through a software layer called Anti Corruption Layer.
This is essential for the scenarios like the following.
If the downstream bounded context comprises a core subdomain ( i.e. the area that has the core functionalities of the business) then the upstream model should be translated and consumed in a manner that the core model is not damaged.³
When the upstream is a “Big Ball of Mud” sort of messy legacy system, the downstream should not conform because it, too, can be messy.³
When the supplier model is subjected to frequent changes, then it is better to translate and consume so that it only affects the Anti Corruption Layer.³
Real-world example:
Consider that a Modernized Event Platform will need to consume events from the legacy event service for the next few years while the rest of the system is gradually modernized.
The Modernized Event Platform has no alternative but to use the Legacy Event Service, but it must ensure that its model remains unaffected by legacy events with encoded information.
A specific transformer library is utilized in this case to convert legacy events to the appropriate format, which is maintained by a separate team in accordance with any changes from the legacy system. When modern events begin to flow, the Modernized Event Platform will no longer need the library.
Open-Host Service
Unlike in the above scenarios, here the consumer has power. So the supplier is obligated to protect consumers by serving in the intended way.
Here, the service is provided by the supplier’s bounded context through a public interface that is independent of its ubiquitous language. This public interface should use a common protocol that consumers can easily adhere to.
The open-host service pattern is a reversal of the anticorruption layer pattern: instead of the consumer, the supplier implements the translation of its internal model.³
Further, Open Host Service can be exposed to multiple bounded contexts in different versions.
Real-world example:
Web Service is a good example of an Open Host service. It is an open standard that is designed to support interoperable machine-to-machine communication over a network. Irrespective of the consumer programming language or the platform it runs Web Services can communicate messages over a network analogous to inter-process communication in a single machine.
Initially, the service provider (bounded context 1) publishes a web service to the web service registry. The service consumer (bounded context 2) can then browse and locate the necessary service URL in the open form. It then receives the WSDL and communicates with it via the SOAP (Simple Object Access Protocol) protocol.
Alright. I hope this article provided you with a good in-depth understanding of how different communication patterns can be used to integrate bounded contexts. In the next article, I am hoping to dive deep into the context of Aggregates in Domain Driven Design.
References
[1] Fowler, Martin. “Bounded Context.” Martin Fowler’s Website. Available at: https://martinfowler.com/bliki/BoundedContext.html#footnote-quote. Accessed [Date].
[2]: Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.
[3]: Khononov, Vladik. Learning Domain-driven Design: Aligning Software Architecture and Business Strategy. O’Reilly Media, Incorporated, 2021.