3.3 Domain-Driven Design and Bounded Context
In his book of the same title, Eric Evans formulated domain-driven design (DDD)5 as pattern language. It is a collection of connected design patterns and supposed to support software development especially in complex domains. In the following text, the names of design patterns from Evan’s book are written in italics.
Domain-driven design is important for understanding microservices, for it supports the structuring of larger systems according to domains. Exactly such a model is necessary for the division of a system into microservices. Each microservice is meant to constitute a domain, which is designed in such a way that only one microservice has to be changed in order to implement changes or to introduce new features. Only then is the maximal benefit to be derived from independent development in different teams, as several features can be implemented in parallel without the need for extended coordination.
DDD defines a basis for how a model for a domain can be designed. An essential foundation of DDD is Ubiquitous Language. This expression denotes that the software should use exactly the same terms as the domain experts. This applies on all levels: in regards to code and variable names as well as for database schemas. This practice ensures that the software really encompasses and implements the critical domain elements. Let us assume for instance that there are express orders in an e-commerce system. One possibility would be to generate a Boolean value with the name “fast” in the order table. This creates the following problem: domain experts have to translate the term “express order,” which they use on a daily basis, into “order with a specific Boolean value.” They might not even know what Boolean values are. This renders any discussion of the model more difficult, for terms have to be constantly explained and related to each other. The better approach is to call the table within the database scheme “express order.” In that case it is completely transparent how the domain terms are implemented in the system.
To design a domain model, DDD identifies basic patterns:
Entity is an object with an individual identity. In an e-commerce application, the customer or the items could be examples for Entities. Entities are typically stored in databases. However, this is only the technical implementation of the concept Entity. An Entity belongs in essence to the domain modeling like the other DDD concepts.
Value Objects do not have their own identity. An address can be an example of a Value Object, for it makes only sense in the context of a specific customer and therefore does not have an independent identity.
Aggregates are composite domain objects. They facilitate the handling of invariants and other conditions. An order, for instance, can be an Aggregate of order lines. This can be used to ensure that an order from a new customer does not exceed a certain value. This is a condition that has to be fulfilled by calculating values from the order lines so that the order as Aggregate can control these conditions.
Services contain business logic. DDD focuses on modeling business logic as Entities, Value Objects, and Aggregates. However, logic accessing several such objects cannot be sensibly modeled using these objects. For these cases there are Services. The order process could be such a Service, for it needs access to items and customers and requires the Entity order.
Repositories serve to access all Entities of a type. Typically, there is a persistency technology like a database behind a Repository.
Factories are mostly useful to generate complex domain objects. This is especially the case when these contain for instance many associations.
Aggregates are of special importance in the context of microservices: within an Aggregate consistency can be enforced. Because consistency is necessary, parallel changes have to be coordinated in an Aggregate. Otherwise two parallel changes might endanger consistency. For instance, when two order positions are included in parallel into an order, consistency can be endangered. The order has already a value of €900 and is maximally allowed to reach €1000. If two order positions of €60 each are added in parallel, both might calculate a still acceptable total value of €960 based on the initial value of €900. Therefore, changes have to be serialized so that the final result of €1020 can be controlled. Accordingly, changes to Aggregates have to be serialized. For this reason, an Aggregate cannot be distributed across two microservices. In such a scenario consistency cannot be ensured. Consequently, Aggregates cannot be divided between microservices.
Building blocks such as Aggregate represent for many people the core of DDD. DDD describes, along with strategic design, how different domain models interact and how more complex systems can be built up this way. This aspect of DDD is probably even more important than the building blocks. In any case it is the concept of DDD, which influences microservices.
The central element of strategic designs is the Bounded Context. The underlying reasoning is that each domain model is only sensible in certain limits within a system. In e-commerce, for instance, number, size, and weight of the ordered items are of interest in regards to delivery, for they influence delivery routes and costs. For accounting on the other hand prices and tax rates are relevant. A complex system consists of several Bounded Contexts. In this it resembles the way complex biological organisms are built out of individual cells, which are likewise separate entities with their own inner life.
To illustrate the system setup in the different Bounded Contexts a Context Map can be used (see section 7.2). Each of the Bounded Contexts then can be implemented within one or several microservices.
Collaboration between Bounded Contexts
How are the individual Bounded Contexts connected? There are different possibilities:
In case of a Shared Kernel the domain models share some common elements; however, in other areas they differ.
Customer/Supplier means that a subsystem offers a domain model for the caller. The caller in this case is the client who determines the exact setup of the model.
This is very different in the case of Conformist: The caller uses the same model as the subsystem, and the other model is thereby forced upon him. This approach is relatively easy, for there is no need for translation. One example is a standard software for a certain domain. The developers of this software likely know a lot about the domain since they have seen many different use cases. The caller can use this model to profit from the knowledge from the modeling.
The Anticorruption Layer translates a domain model into another one so that both are completely decoupled. This enables the integration of legacy systems without having to take over the domain models. Often data modeling is not very meaningful in legacy systems.
Separate Ways means that the two systems are not integrated, but stay independent of each other.
In the case of Open Host Service, the Bounded Context offers special services everybody can use. In this way everybody can assemble their own integration. This is especially useful when an integration with numerous other systems is necessary and when the implementation of these integrations is too laborious.
Published Language achieves similar things. It offers a certain domain modeling as a common language between the Bounded Contexts. Since it is widely used, this language can hardly be changed anymore afterwards.
Bounded Context and Microservices
Each microservice is meant to model one domain so that new features or changes have only to be implemented within one microservice. Such a model can be designed based on Bounded Context.
One team can work on one or several Bounded Contexts, which each serve as a foundation for one or several microservices. Changes and new features are supposed to concern typically only one Bounded Context—and thus only one team. This ensures that teams can work largely independently of each other. A Bounded Context can be divided into multiple microservices if that seems sensible. There can be technical reasons for that. For example, a certain part of a Bounded Context might have to be scaled up to a larger extent than the others. This is simpler if this part is separated into its own microservice. However, designing microservices that contain multiple Bounded Contexts should be avoided, for this entails that several new features might have to be implemented in one microservice. This interferes with the goal to develop features independently.
Nevertheless, it is possible that a special requirement comprises many Bounded Contexts—in that case additional coordination and communication will be required.
The coordination between teams can be regulated via different collaboration possibilities. These influence the independence of the teams as well: Separate Ways, Anticorruption Layer or Open Host Service offer a lot of independence. Conformist or Customer/Supplier on the other hand tie the domain models very closely together. For Customer/Supplier the teams have to coordinate their efforts closely: the supplier needs to understand the requirements of the customer. For Conformist, however, the teams do not need to coordinate: one team defines the model that the other team just uses unchanged (see Figure 3.5).
Figure 3.5 Communication Effort of Different Collaborations
As in the case of Conway’s Law from section 3.2, it becomes very apparent that organization and architecture are very closely linked. When the architecture enables a distribution of the domains in which the implementation of new features only requires changes to a defined part of the architecture, these parts can be distributed to different teams in such a way that these teams can work largely independently of each other. DDD and especially Bounded Context demonstrate what such a distribution can look like and how the parts can work together and how they have to coordinate.
With large-scale structure, DDD also addresses the question how the system in its entirety can be viewed from the different Bounded Contexts with respect to microservices.
A System Metaphor can serve to define the fundamental structure of the entire system. For example, an e-commerce system can orient itself according to the shopping process: the customer starts out looking for products, then he/she will compare items, select one item, and order it. This can give rise to three microservices: search, comparison, and order.
A Responsibility Layer divides the system into layers with different responsibilities. Layers can call other layers only if those are located below them. This does not refer to a technical division into database, UI and logic. In an e-commerce system, domain layers might be (for example) the catalog, the order process, and billing. The catalog can call on the order process, and the order process can call on the generation of the bill. However, calls into the other direction are not permitted.
Evolving Order suggests it is best not to determine the overall structure too rigidly. Instead, the order should arise from the individual components in a stepwise manner.
These approaches can provide an idea how the architecture of a system, which consists of different microservices, can be organized (see also Chapter 7, “Architecture of Microservice-based Systems”).