Introduction
With my term project “A Domain-specific Language for Service Decomposition” I worked on an approach aiming to formalize an interpretation of the strategic DDD patterns and how they can be combined. The resulting Context Mapper Domain-specific Language (DSL), provides a foundation for structured service decomposition approaches such as Service Cutter. The decomposition of applications into appropriately sized services towards Service-oriented a.k.a. Microservice architectures is challenging. The DSL allows to create DDD context maps and use them as input for the Service Cutter tool. Other decomposition approaches using the DSL will follow with the next project. You can further convert the context maps into PlantUML component and class diagrams. The full project report is published by HSR and can be found here.
Strategic Domain-driven Design (DDD)
DDD provides an approach to decompose a domain into multiple bounded contexts. Thereby its patterns and especially the bounded contexts have gained more attention within the last years, due to the Microservice architectures trend. At the same time, there are different interpretations and a certain ambiguity regarding the original pattern definitions of Evans and how they can be combined. Therefore, this project aims to provide a formal definition of such an interpretation. Note that it reflects my interpretation based on the DDD literature such as Evans, Vernon, Brandolini, personal experience and inputs of my project supervisor. I’m very interested in other opinions and I see the Context Mapper project as a work in progress which hopefully will be developed further by incorporating valuable feedback out of the DDD community.
The following domain model illustrates the structure on which the Context Mapper DSL is based on. The model’s main object is the context map which contains multiple bounded contexts and their relationships. Each bounded context implements parts of given subdomains.
Regarding the relationship patterns, we differentiate between symmetric and asymmetric (Upstream/Downstream) relationships. With asymmetric relationships all relationships with an Upstream and a Downstream are meant. The symmetric relationships, namely Partnership and Shared Kernel, describe intimate relationships where no Upstream and/or Downstream can be clearly defined. In other words, in these relationships both bounded contexts depend on the other one. Asymmetric relationships in contrast have the characteristic that only one of the two contexts depends on the other. As Evans and Vernon, I use the terms Upstream and Downstream. The Downstream context is the one who depends on the Upstream context, whereas the Upstream context is not dependent on the Downstream context.
The Customer/Supplier pattern describes, according to my interpretation, a special case of an Upstream/Downstream relationship where the Upstream is the Supplier and the Downstream the Customer. The model above tries to differentiate the two cases by using the term Generic Upstream/Downstream Relationship for Upstream/Downstream relationships which are not Customer/Supplier relationships. However, an upstream or downstream bounded context can implement certain relationship patterns, or Relationship Role’s as I call them. The patterns which can be implemented by an upstream bounded context are Published Language (PL) and Open Host Service (OHS). The corresponding downstream roles are Anticorruption Layer (ACL) and Conformist.
Possible Combinations
There are different opinions on how these strategic DDD patters can be combined. This approach and the implemented Context Mapper reflects my interpretation based on the DDD literature. The meta-model above already implies certain rules regarding applicability.
First, the patterns PL, OHS, ACL and Conformist are only applicable in Upstream/Downstream relationships. In a Shared Kernel relationship the bounded contexts are sharing common code, typically a library, which is used for their communication. Both bounded contexts depend on that code and the teams manage it together. The application of one of the four mentioned patterns is contradictory within such a situation. The same applies to the Partnership pattern, since it clearly states that both bounded contexts depend on each other. Both contexts “fail or succeed together”. This means that if one of the bounded contexts fails, the other automatically fails as well. An application of the four patterns PL, OHS, ACL and Conformist is somehow contradictory again, since there can not be an upstream or downstream in such a relationship. The application of one of the patterns would require to declare one of the contexts upstream and the other downstream. And, as already mentioned, an upstream never depends on a downstream or “fails” if one of the downstreams does.
Further, the model implies that the patterns PL and OHS are only applicable on upstream bounded contexts, and the patterns ACL and Conformist only on downstream contexts. This seems to be obvious in my opinion. However, whereas on an upstream context both patterns PL and OHS can be applied together, only one of the patterns ACL and Conformist can be applied on a downstream context. The two downstream roles describe two different solutions a team can apply in case they are the downstream in a Upstream/Downstream relationship where the upstream does not really care about the needs of the downstreams. The downstream can either decide to “conform” to the upstream or to implement an ACL, but both patterns at the same time does not seem to make much sense.
The special case of a Customer/Supplier relationship probably raises the most questions regarding combinations with the other relationship patterns. In my opinion, the combination with the OHS pattern leads to contradictions and it is therefore not allowed within the Context Mapper DSL. Whereas the Customer/Supplier pattern says that the supplier has to prioritize his implementation tasks according to the wishes of the customer, the OHS pattern somehow implies that the upstream implements “one solution for all” downstreams. This would mean that the supplier has to consolidate all the wishes of many different customers into one single API, which is not a very likely scenario from my experience. At some point a customer will have some special requirements which are not needed by the other customers and an implementation within the same public API would pollute the API which should be kept clean. In this situation the upstream team may implement another specialized API for that customer and that special purpose. However, in this case the relationship between this downstream/customer and the upstream/supplier implementing the OHS is no longer clearly defined by the communication over the OHS and it would be misleading to declare it this way.
Another contradictory combination according to my interpretation is Customer/Supplier with Conformist. The customer within a Customer/Supplier relationship should not be in the situation where it has to “conform” to the supplier, since the supplier should implement the common model according to the customers requirements. For the same reason I tend to argue that the combination ACL and Customer/Supplier is contradictory. However, I allowed this combination in Context Mapper, since it is also possible to argue that an ACL can make sence in any case. At the same time I would say that this ACL should not need a defensive character in a Customer/Supplier relationship and might be better called a “translation layer”.
A Domain-specific Language (DSL) as a formal approach for DDD
The Context Mapper DSL provides a tool to create context maps according to the meta-model introduced above in a formal manner. In the examples repository you can find two examples for such context maps. The first is based on a fictitious insurance company and the second on the DDD Sample Application.
The following illustration shows the context map of the insurance scenario inspired by the representation of Vernon and Brandolini. The complete example and short descriptions of the bounded contexts can be found here.
This context map can be described in the Context Mapper language (CML) as follows:
/* Example Context Map written with 'ContextMapper DSL' */
ContextMap {
type = SYSTEM_LANDSCAPE
state = TO_BE
/* Add bounded contexts to this context map: */
contains CustomerManagementContext
contains CustomerSelfServiceContext
contains PrintingContext
contains PolicyManagementContext
contains RiskManagementContext
contains DebtCollection
/* Define the contexts relationships */
CustomerSelfServiceContext -> CustomerManagementContext : Customer-Supplier
CustomerManagementContext -> PrintingContext : Upstream-Downstream {
implementationTechnology = "SOAP"
upstream implements OPEN_HOST_SERVICE, PUBLISHED_LANGUAGE
downstream implements ANTICORRUPTION_LAYER
}
PrintingContext <- PolicyManagementContext : Upstream-Downstream {
implementationTechnology = "SOAP"
upstream implements OPEN_HOST_SERVICE, PUBLISHED_LANGUAGE
downstream implements ANTICORRUPTION_LAYER
}
RiskManagementContext <-> PolicyManagementContext : Partnership {
implementationTechnology = "RabbitMQ"
}
PolicyManagementContext -> CustomerManagementContext : Upstream-Downstream {
implementationTechnology = "RESTful HTTP"
upstream implements OPEN_HOST_SERVICE, PUBLISHED_LANGUAGE
downstream implements CONFORMIST
}
DebtCollection -> PrintingContext : Upstream-Downstream {
implementationTechnology = "SOAP"
upstream implements OPEN_HOST_SERVICE, PUBLISHED_LANGUAGE
downstream implements ANTICORRUPTION_LAYER
}
PolicyManagementContext <-> DebtCollection : Shared-Kernel {
implementationTechnology = "Shared Java Library, Communication over RESTful HTTP"
}
}
The asymmetric relationships are declared with the <-> arrows in both directions, whereas the Upstream/Downstream relationships are declared with an arrow (<- or ->) pointing from the downstream towards the upstream (illustrating the dependency). Check our Language Reference for more details about the language. Further note that the example above does not contain the bounded context specifications. The complete CML code of the example can be found here. With the integration of the Sculptor DSL, CML allows to specify the bounded contexts in terms of the tactic DDD patterns.
Service Decomposition
A model written in the Context Mapper DSL can be used as input for Service Cutter in order to improve the boundaries of the bounded contexts in terms of Service Cutter’s coupling criteria. The following illustration shows the Service Cutter result for the insurance example introduced above.
The result perfectly represents the modeled bounded contexts and thus confirms a good coupling between the bounded contexts.
- Service A: Printing Context
- Service B: Risk Management Context
- Service C: Policy Management Context
- Service D: Debt Collection Context
- Service E: Customer Management Context
- Service F: Customer Self-Service Context
You can generate the Service Cutter input files using Context Mapper’s Eclipse plugin, which you find here.
Generating Graphical Representations
A DSL offers the possibility the transform your model into any other representation. Besides the transformation into Service Cutter input files, Context Mapper currently allows to generate PlantUML diagrams as graphical representation of your context map. The generator creates a UML component diagram, illustrating the bounded contexts as components. It further creates corresponding interfaces and connections between the components according to the bounded context relationships.
The following image shows the generated component diagram for the insurance example:
The generator further creates a UML class diagram for every bounded context. This page describes how you can generate the UML diagrams out of context maps written in Context Mapper DSL.
Future Work
In the next term project, starting in February 2019, our goal is to work on other service decomposition approaches using the presented DSL. By applying architectural refactorings a context map written in the Context Mapper DSL could be decomposed by splitting and merging bounded contexts stepwise. Other structured decomposition approaches such as Service Cutter using other algorithms and heuristics could be applied as well.
Other future projects implementing other generators would be conceivable. Such transformations could provide the possibility to generate other graphical representations or code. A code generator which creates microservice stubs (project templates) for the modeled bounded contexts might be an interesting feature.