When thinking about collaborative relationships between bounded contexts, first we need to determine if there is a relationship, then what kind of relationship, and finally, based on the impact of the change, whether we need to introduce a preservative layer, open hosting services, and so on. If the collaboration is found to be unreasonable, we need to reflect on whether the bounded context we identified earlier is reasonable.

The influence of communication boundaries in bounded context on collaboration

Determine the relationship between the bounded context can’t take it for granted that two bounded context needs to fully consider participating in collaborative business scenarios, and then in the scene to identify causes of dependence between them, determine the direction of dependence, and then determine the integration point, it is important to note that the bounded context boundary for the definition of collaboration of communication is crucial. The communication boundary of bounded context can be divided into in-process boundary and interprocess boundary. This communication boundary will directly affect our choice of context mapping mode. For example, with interprocess boundaries, you need to consider the cost of cross-process access, such as serialization and deserialization, network overhead, and so on. Due to the limitations of cross-process calls, different access protocols between each other, and the need to control the changes that may be introduced by upstream limiting contexts, a typical collaboration approach is to introduce both open host Services (OHS) and anti-corrosion layers (ACLs), as shown in the following figure:

The bounded context A externally exposes REST services to the user interface layer through controllers, and internally invokes Application Services at the Application layer, and then invokes Domain Models at the Domain layer. If the bounded context A needs to access the services of bounded context B, it is accessed through the Interface placed at the domain layer, but the actual access logic is implemented by the infrastructure layer Client, which is the anticorrosive layer of the context-mapped pattern. The client is actually accessing the controller of bounded context B, which is in the infrastructure layer and is equivalent to the open host service of the context-mapped pattern. Bounded context B accesses bounded context C in exactly the same way, in which the database is accessed through the Repository interface via the Persistence component.

As you can see from the figure, we need to consider hierarchical architectural design when defining the collaboration of bounded contexts. Typically, we think of the application, domain, and infrastructure layers of a layered architecture as being within the boundaries of a bounded context. If the bounded context does not adopt a “zero-sharing architecture,” then coupling at the database layer also needs to be considered when considering collaboration relationships. The exception is the user interface layer of the layered architecture, which is not a bounded context and is not usually considered in domain modeling. The reason for this is that the user interface layer has a completely different perspective from the domain. The user interface layer focuses on the user experience, not the vertical division of the business, let alone the high cohesion and loose coupling between the businesses. In many cases, for the convenience of user operations, to reduce the number of user operations, and to improve the user experience, it is possible to aggregate many businesses in different bounded contexts in one UI page. We can look at the pages of Amazon or JINGdong, for example, under the page of “My Jingdong”, almost all the businesses in the whole e-commerce system are captured in a single sweep. This does not comply with our classification of bounded contexts. In fact, in a “back-end separation” architecture, the user interface layer tends to act as the caller of back-end services and should certainly be excluded from the bounded context.

There is a design decision to be made: is it necessary to introduce open mainframe services and corrosion protection? This requires designers to weigh the priorities of change, code reuse, and architectural simplicity. There is no right answer, but you need to look at specific application scenarios to help you decide. I certainly can’t run out of business scenarios, and the ones presented here are just one of them. For example, if the limiting context uses in-process communication, it is worth considering whether the downstream limiting context also needs to be accessed through clients and controllers. Preserving clients and their interfaces is necessary if the future evolution from in-process to interprocess communication is to be considered.

Note: The above knowledge of bounded context communication boundaries, domain-driven design layered architecture, zero-sharing architecture, code model structure, and northbound gateway and southbound gateway will be elaborated in the following sections.

Collaboration is dependence

If there is a collaboration between bounded contexts, there must be some reason for the collaboration. From a dependency perspective, this collaboration occurs because one party needs to “know” the other’s knowledge, which includes:

Domain behavior: Need to determine what causes coupling between behaviors? In the case of upstream and downstream relationships, determine whether the downstream is the true caller of the upstream service. Domain model: Do you need to reuse someone else’s domain model, or do you need to redefine your own? Data: Whether the database corresponding to the bounding context is required to provide the operational data that supports the business behavior.

Dependencies generated by domain behavior

The so-called domain behavior, at the design level, is actually the responsibility of each domain Object, which can be undertaken by Entity and Value Object. It can also be a Domain Service or Repository or even a Factory object.

There are three ways in which an object can perform its duties:

Do all the work yourself. Ask other objects to help do some of the work (collaborate with other objects). Delegate the entire service request to another help object.

If we choose the latter two forms of responsibility, cooperation between objects is necessarily involved. In a good design, responsibilities must be “divisible”, where each cohesive object takes on only the parts it is good at, and transfers responsibilities it is not good at to other objects. Christepher Alexander, author of The Timeless Way of Architecture, suggests using less centralized mechanisms when dealing with design problems. Again in Object Design: Roles, Responsibilities, and Collaboration, the authors argue that:

Software objects are linked together through interactions and shared responsibilities. Establishing a simple, consistent communication mechanism between objects avoids centralization of the solution, and the impact of local changes should not spread throughout the system, which is required for the strong adaptability of the system. Complex software systems are easier to manage when responsibilities are divided, organized, and collaboration follows predictable patterns.

The bounded context proposed by domain-driven design is actually “decentralization” at the architectural level. Through its boundaries, “responsibility can be divided and organized”, and the collaboration between bounded contexts “follows the predictable pattern”, which can effectively control business complexity and technical complexity. Therefore, when considering the cooperation relationship between bounded contexts, it is crucial to distinguish these separated responsibilities and make clear whether objects in bounded contexts cooperate or object cooperation between bounded contexts. The following two aspects are mainly considered:

Who will perform the duties? This involves which bounded context the domain behavior should be placed in. Who initiates the call to this responsibility? If the caller and the responder are in different bounded contexts, it means that the two are cooperating and can help us determine the upstream and downstream relationship.

Take the order function of e-commerce system as an example. Consider a business scenario where a customer has selected an item to purchase and submits an order through the shopping cart, which involves a domain behavior: submit an order. Assuming that the customer belongs to the customer context and the order belongs to the order context, you now need to consider who is responsible for submitting the order.

From the realistic model of the e-commerce system, the domain behavior is initiated by the Customer, that is to say, the Customer should have the behavior of submitting the order. Does this mean that the behavior should be assigned to the Customer aggregation root? In fact, we need to pay attention to the difference between reality models and domain models, especially object models. In the “Place an order” business scenario, Customer is a participant and plays the role of buyer. One view of domain modeling holds that the domain model is a model of the objective world that excludes the participants, and the Customer as the participant should be excluded from the model.

Of course, this view is controversial. For example, four-color modeling does not. Four-color modeling suggests introducing role objects between temporal objects and entity objects as people, that is, role objects as part of the domain model. Of course, we should not directly equate roles with model actors. In the Data Context Interation (DCI) mode, it is necessary to think about the collaboration between them by identifying roles in a Context. For example, in the transfer business scenario, the bank Account, as a Data object, participates in the collaboration of the transfer context, and two role objects, Source and Destination, should be abstracted.

Note: In tactical design, I will delve into the relationship and modeling details between domain modeling, four-color modeling and DCI.

The determination of domain models is always controversial, because everyone looks at domain models differently and has a different view of design. Domain model should be finally implemented into code implementation and handed over to practice to verify the rationality of design. Do not get too entangled in the details of modeling in the process of domain modeling, but just choose a reasonable model. In fact, it is an iterative process from modeling to design, and then from design to coding development. If there are indeed defects in the model during implementation, you can go back and modify it. It is a waste of time to strive for the perfection of the domain model. Before applying design principles properly, it is important to clarify: what problem are we trying to solve? The question here is not how to determine the domain model, but to whom should the act of submitting an order be assigned? First, it involves assigning responsibility to objects. From the perspective of semantic relevance, although the domain behavior is initiated by the customer, the information (knowledge) subject of the operation is actually the order, which means that they should be assigned to the order context. In fact, this allocation also conforms to the “information expert mode” of object-oriented design principles, that is, “the holder of information is the expert operating the behavior”; Second, from a hierarchical architecture point of view, the so-called “client-initiated invocation” here only means that the client initiates a request to the back-end service through the user interface layer, in other words, it is not invoked by a Customer domain object belonging to the Customer context.

As we will see later, the domain layer should be at the heart of the bounded context if the idea of a clean architecture is followed. To ensure the integrity of business use cases and avoid exposing too many details of domain collaboration, domain-driven design introduces an application layer, which wraps the entire domain layer. However, the application layer does not communicate directly with the front end as the caller, usually by introducing RESTful services that are equivalent to open host services (OHS) in context mapping and controllers in MVC pattern. Components that belong to the infrastructure layer. For the order placing scenario, the customer invokes the OrderController in the USER interface layer. After receiving the request, the OrderController handles the validation and transformation of the request message, and then transfers responsibility to OrderAppService, which accesses PlaceOrderService, the domain service in the domain layer, as shown below:

The implementation code for the order placing scenario is as follows:

Since PlaceOrderService, OrderAppService, and OrderController are all part of the order context, and the real initiator of this action call is not a Customer domain object, but a user interacting with the system through the user interface, Therefore, in this business scenario, there is no dependency of the customer context on the domain behavior of the order context as we imagine because the customer places the order.

When assigning call responsibilities to the front end, we need to be careful not to decouple the backend bound contexts by delegating all the work to the front end. The front end is definitely the best place to make calls, but only if we don’t let the front end take over the business logic that the back end is supposed to encapsulate. When one domain behavior becomes an execution step “embedded” in another domain behavior, the initiating caller is no longer the front-end UI, because that execution step forms part of the business logic. For example, when calculating the total price of an order, different promotion strategies need to be determined according to the category of the customer, and then the total price of the order is calculated according to the promotion strategy, which involves four field behaviors:

Calculate the total price of an order Obtain a customer category determine a promotional strategy Calculate a promotional discount The next three domain actions all provide the functional support for “calculate the total price of an order”, which is the previously called “embedded” execution step. In addition to the behavior that the total price of the order is in the order context, the acquisition of customer categories is in the customer context, and the promotion strategy and discount calculation are in the promotion context. Because domain behavior dependencies are created, they act as upstream limiting contexts for the order context.

There is actually a design change here, depending on the layering of responsibilities (as described in the domain scenario analysis explained earlier) :

Calculate the total order price — Order context gets customer category — Customer context gets promotion strategy based on customer category — Promotion context Calculates promotion discount based on promotion strategy — Promotion Context When using this responsibility hierarchy, the customer context and promotion context are upstream of the order context. If we view the acquisition of customer categories as business logic within the promotional context, the hierarchical structure of responsibility becomes:

Calculate order sum – order context to get the promotion strategy, sales promotion context for customer category – context According to the customer gain promotion strategy, sales promotion context promotional discounts, promotional context through the calculation of promotion strategy This time, order upstream of the context for promotional context, context and in the promotion, The domain behavior of the client context needs to be invoked.

We can even further encapsulate responsibility. As for the calculation of the total price of the order, it actually does not care how the promotion discount is obtained, that is to say, the responsibility of obtaining the promotion strategy is actually to calculate the details of the promotion discount, so the hierarchical structure of the responsibility changes again:

  • Calculate the total order price — order context calculates the promotion discount — Promotion context obtains the promotion strategy — Promotion context obtains the promotion strategy based on the customer category — Promotion context

Calculate promotional discounts from promotional strategies — Promotional context

This design reduces collaboration between other bounded contexts and the order context, and reduces dependent domain behavior when working with each other. For example, if we want to reduce the coupling between the order context and the promotion context to avoid the impact of possible changes in the promotion context on the order context, we can introduce an anticorrosion layer in the context mapping. Since the order context only needs to know the domain behavior responsibility of “calculating promotional discounts”, the design of the corrosion layer interface is much easier:

Obviously, different responsibility layers will directly affect our judgment of collaboration in bounded contexts. At the end of the day, it’s the “knowledge” you need to know about each other that makes the difference. We should follow the law of minimum knowledge as far as possible, and on the premise of ensuring the reasonable distribution of responsibilities, the less limiting context that produces collaboration, the better.