At SummerSoC 2020 I present(ed) my (Stefan Kapferer) and Olaf Zimmermann’s paper on «Domain-driven Service Design» (accepted; to be published soon)

The paper covers the following topics:

• Service decomposition with Strategic Domain-driven Design (DDD)
• Context Mapper DSL (CML) as a machine-readable approach to DDD Context Mapping
• Decomposition criteria: How to decompose a domain and identify «Bounded Contexts»?
• Architectural Refactorings (ARs) as model transformation for CML
• An incremental method to decompose domains «step by step»
• Service contract generation out of the DDD models (with the MDSL language)

Since its not possible to cover all the topics in a 20 minutes presentation, I focus(ed) on Strategic DDD, the Context Mapper DSL (CML), and the incremental decomposition method with Architectural Refactorings (ARs). In this blogpost I provide CML code for the examples I use(d) and continue the process towards service contract generation with the Microservice Domain-specific Language (MDSL) (developed by Olaf Zimmermann).

## Strategic Domain-driven Design and Context Mapper

The question how to decompose a software system into modules and/or services has gained much attention with the recent Microservice trend. The question is however not new. Parnas already wrote a paper «On the criteria to be used in decomposing systems into modules» in 1972.

One popular answer these days is Strategic Domain-driven Design (DDD). It emphasizes the need for a distinctive vocabulary, the ubiquitous language, in the domain model and suggests to decompose a domain into so-called «Bounded Contexts». A domain typically consists of multiple subdomains. In our presentation example: the insurance domain consists of subdomains such as customers, policies, risks, etc.

When implementing such a system with a service-oriented architectural style, microservices, or a modular monolith («modulith»), you are confronted with the question how many (micro-)services or modules the system shall exhibit. DDD practitioners suggest to identify Bounded Contexts and implement one subsystem (service or module) per context. A Bounded Context establishes a boundary within which a particular model is valid.

The concepts and domain objects inside a Bounded Context shall be defined clearly and distinctively (the ubiquitous language). As the following example illustrates, a Bounded Context can implement parts of one or multiple subdomains:

When we decompose our domain into multiple Bounded Contexts the question how this contexts integrate with each other raises naturally. This is where DDD Context Maps and context mapping come into play. A Context Map illustrates the relationships between Bounded Contexts and how the data between them flow. The following Context Map shows one example for our fictitious insurance scenario:

The «U» and «D» at some of the relations mean «upstream» and «downstream». The data in «Upstream-Downstream» relationships always flow from the upstream to the downstream. That means: the upstream exposes a part of its domain model (for example via a public API) to the downstream. The downstream context can either conform to the upstreams model (CONFORMIST), or implement an Anticorruption Layer (ACL) to protect its own model from upstream changes. Upstream patterns are Open Host Service (OHS) and Published Language (PL). The OHS pattern is applied if the upstream context implements a public and open API that is supposed to be used by multiple downstream context in a unified manner. The published language (PL) is the part of the domain model that is exposed by the upstream - which is why this pattern is often used in combination with OHS. If a relationship is marked with «Customer/Supplier», the two teams work closely together in a sense that the upstream team prioritizes its work according to the downstream (customer) requirements. In symmetric relationships (Partnership and Shared Kernel) there exist interdependecies which lead to a stronger coupling in comparison to Upstream-Downstream relationships. For more details about the DDD patterns, we recommend the following books and resources:

Context Mapper offers a Domain-specific Language (DSL) to model DDD Context Maps and Bounded Contexts in a machine-readable manner. DDD practitioners have drawn Context Maps manually on paper so far. One of our goals with Context Mapper was to reduce ambiguities regarding strategic DDD patterns and provide a clear and precise interpretation of how the patterns can be combined. We further believe that DDD Context Maps are artifacts that must evolve iteratively. The DSL approach comes with a few benefits:

• Precise definition of what can be combined (semantic language rules/validators)
• Models can be processed and transformed
• Allows the implementation of model transformations/refactorings
• Possibility to generate other representations of the model
• Currently:
• PlantUML component and class diagrams
• Graphical Context Maps (as shown above)
• Service contracts (MDSL)
• Microservice code (via JHipster)

The fictitious insurance example Context Map (as illustrated above) can be written in Context Mapper DSL (CML) as follows:

ContextMap InsuranceContextMap {
/* Add bounded contexts to this context map: */
contains CustomerManagementContext, CustomerSelfServiceContext, PrintingContext
contains PolicyManagementContext, RiskManagementContext, DebtCollection

/* Define the context relationships: */
CustomerSelfServiceContext [D,C]<-[U,S] CustomerManagementContext

CustomerManagementContext [D,ACL]<-[U,OHS,PL] PrintingContext

PrintingContext [U,OHS,PL]->[D,ACL] PolicyManagementContext

RiskManagementContext [P]<->[P] PolicyManagementContext

PolicyManagementContext [D,CF]<-[U,OHS,PL] CustomerManagementContext

DebtCollection [D,ACL]<-[U,OHS,PL] PrintingContext

PolicyManagementContext [SK]<->[SK] DebtCollection
}

/* Bounded Context definitions */
BoundedContext CustomerManagementContext implements CustomerManagementDomain {
/* can contain domain model based on tactic DDD patterns */
}

BoundedContext CustomerSelfServiceContext implements CustomerManagementDomain

BoundedContext PrintingContext implements PrintingDomain

BoundedContext PolicyManagementContext implements PolicyManagementDomain

BoundedContext RiskManagementContext implements RiskManagementDomain

BoundedContext DebtCollection implements DebtsDomain

/* domain & subdomain definitions removed to save space */


Note: Context Mapper not only supports modeling Bounded Contexts, you can also model your domain with its subdomains and specify which Bounded Contexts implement which subdomains. In addition, you can start your modeling process by declaring use cases or user stories and derive your subdomains and Bounded Contexts automatically. I don’t explain this process here, as my colleague Olaf Zimmermann already covers this in his blogpost featuring our rapid prototyping transformations.

## Decomposition Criteria

We have now seen how we can model DDD Context Maps and therefore describe system architectures in terms of Bounded Contexts. But this does still not really answer the question how we can identify Bounded Contexts. Which criteria and heuristics can we use to do that?

We distilled a set of «Decomposition Criteria» (DCs) empirically. We used research papers (such as the one of D.L. Parnas «On the Criteria To Be Used in Decomposing Systems into Modules»), resources of DDD experts (books, blogposts, conference talks), and our own experience in software projects. Here just a few of often mentioned decomposition criteria:

• Use Cases
• Ownership and teams (Conway’s law)
• Language and domain expert boundaries
• Data flow
• Non-functional requirements (NFRs) such as security, availability, etc.

These are all criteria that can be reasons to split a Bounded Context or extract parts of a domain model into a separate Bounded Context. The InfoQ article on Context Mapping by Alberto Brandolini illustrates how such criteria can be used to identify Bounded Contexts and decompose an initial domain «step by step».

## Architectural Refactorings (ARs) as Model Transformations

One of the advantages of having a machine-readable Context Map: we can implement model refactorings for our DSL. Thus, we can implement refactorings that allow users to decompose the described domain with tool-support.

Based on the decomposition criteria mentioned above we proposed a set of «Architectural Refactorings (ARs)»:

One example: «Split Bounded Context by Owner (AR-3)» ensures that only one team works on one Bounded Context. It can be applied if multiple teams work on the same context. The result of the refactoring: one Bounded Context per team. This increases team autonomy and establishes clear responsibilities.

We implemented this ARs as model transformations/refactorings for the CML language. You find an overview over all implemented ARs here. In the following I illustrate the application of «Split Bounded Context by Owner (AR-3)» on a CML model (as in my SummerSoC presentation).

Assume we have the following Bounded Context in our CML model (I still use the fictitious insurance scenario):

/* Bounded Context Definitions */
BoundedContext CustomerManagementContext {
type = FEATURE
domainVisionStatement = "The customer management context is responsible for managing all the data of the insurance companies customers."
implementationTechnology = "Java, JEE Application"

Aggregate CustomersMainAggregate {
owner CustomerBackendTeam

Entity Customer {
aggregateRoot

- SocialInsuranceNumber sin
String firstname
String lastname
}

String street
int postalCode
String city
}

ValueObject SocialInsuranceNumber {
String sin key
}
}

Aggregate CustomerSelfServiceAggregate {
owner CustomerFrontendTeam

Entity UserAccount {
- Customer accountCustomer
}
aggregateRoot

- UserAccount issuer
}
}

}

/* Team's: */
BoundedContext CustomerFrontendTeam { type TEAM }
BoundedContext CustomerBackendTeam { type TEAM }


The given Bounded Context contains two Aggregates. With the owner keyword, CML allows to assign the team that owns a given Aggregate. A team is a Bounded Context as well (in CML modeled with type TEAM). This describes a situation where teams and system or feature Bounded Contexts are not aligned. Multiple teams work on the same Bounded Context. In the Context Mapper IDE (VS Code extension, Eclipse Plugin, or online) we can not apply «Split Bounded Context by Owner» on this context:

For the model given above the refactoring will produce the following output:

/* Bounded Context Definitions */
BoundedContext CustomerManagementContext {
domainVisionStatement = "The customer management context is responsible for managing all the data of the insurance companies customers."
type FEATURE
implementationTechnology "Java, JEE Application"

Aggregate CustomerSelfServiceAggregate {
owner CustomerFrontendTeam
Entity UserAccount {
- Customer accountCustomer
}
aggregateRoot
- UserAccount issuer
}
}
}

BoundedContext NewBoundedContext1 {
Aggregate CustomersMainAggregate {
owner CustomerBackendTeam
Entity Customer {
aggregateRoot
- SocialInsuranceNumber sin
String firstname
String lastname
}
String street
int postalCode
String city
}
ValueObject SocialInsuranceNumber {
String sin key
}
}
}

/* Team's: */
BoundedContext CustomerFrontendTeam {
type TEAM
}

BoundedContext CustomerBackendTeam {
type TEAM
}


As you can see, the resulting model contains one Bounded Context per team. By using the «Rename» refactoring we can improve the naming of the given Bounded Contexts: (we also post-processed the Bounded Context attributes a bit)

/* Bounded Context Definitions */
BoundedContext CustomerSelfServiceContext {
domainVisionStatement = "The customer self-service context provides a web application where customers can change their address."
type FEATURE
responsibilities "Handle address change requests of customers"
implementationTechnology "React Web Application"

Aggregate CustomerSelfServiceAggregate {
owner CustomerFrontendTeam
Entity UserAccount {
- Customer accountCustomer
}
aggregateRoot
- UserAccount issuer
}
}
}

BoundedContext CustomerManagementContext {
domainVisionStatement = "The customer management context is responsible for managing all the data of the insurance companies customers."
type FEATURE
implementationTechnology "Java, JEE Application"

Aggregate CustomersMainAggregate {
owner CustomerBackendTeam
Entity Customer {
aggregateRoot
- SocialInsuranceNumber sin
String firstname
String lastname
}
String street
int postalCode
String city
}
ValueObject SocialInsuranceNumber {
String sin key
}
}
}

/* Team's: */
BoundedContext CustomerFrontendTeam {
type TEAM
}

BoundedContext CustomerBackendTeam {
type TEAM
}


Note: The refactorings also ensure that Context Map relationships (if you have any) stay consistent and are corrected if necessary (not shown above in order to keep the example shorter/simpler).

## Incremental Service Decomposition

In the refactoring example above we have shown one single decomposition step. We believe that decomposing a software system or domain into a Context Map (multiple Bounded Contexts) is an iterative and evolutionary process. By providing multiple ARs that are based on different decomposition criteria, one can decompose a domain with Context Mapper «step by step». The following graphic illustrates this idea:

Users can start with the domain analysis and model their domain as one single context first. By applying the ARs on the strategic DDD level (see image above), users can decompose the domain «step by step» and evolve a Context Map iteratively. We also offer refactorings on the tactic DDD level that allow to decompose and merge Aggregates inside a Bounded Context.

Note: Have a look into our rapid prototyping tutorial and Olaf Zimmermann’s blogpost to learn how you can derive Bounded Contexts from subdomains automatically. These model transformations ease the shift from the «domain analysis» phase into the «Stratgic DDD» phase as illustrated above. Once you derived a Bounded Context from one or multiple subdomains, you can decompose it by using the ARs.

## Service Contract Generation

As soon as you prototyped your Context Map, the API’s that integrate your subsystems (Bounded Contexts) become an important design issue. How shall they be designed/implemented and which parts of your domain models are exposed to other Bounded Contexts? Context Mapper offers you the possibility to generate MDSL contracts out of your CML Context Map. By using the MDSL tools you can even generate technology-specific contracts and code to rapidly prototype your applications later.

In CML you can declare which Aggregates are exposed in a Context Map relationship. Have a look at the following example:

/* Example Context Map written with 'ContextMapper DSL' */
ContextMap InsuranceContextMap {
type = SYSTEM_LANDSCAPE
state = TO_BE

contains CustomerManagementContext, CustomerSelfServiceContext

CustomerManagementContext [OHS, PL]->[ACL] CustomerSelfServiceContext {
exposedAggregates CustomersMainAggregate
implementationTechnology "RESTful HTTP"
}
}

/* Bounded Context Definitions */
BoundedContext CustomerSelfServiceContext {
domainVisionStatement = "The customer self-service context provides a web application where customers can change their address."
type FEATURE
responsibilities "Handle address change requests of customers"
implementationTechnology "React Web Application"

Aggregate CustomerSelfServiceAggregate {
Entity UserAccount {
- Customer accountCustomer
}
aggregateRoot
- UserAccount issuer
}
}
}

BoundedContext CustomerManagementContext {
domainVisionStatement = "The customer management context is responsible for managing all the data of the insurance companies customers."
type FEATURE
implementationTechnology "Java, JEE Application"

Aggregate CustomersMainAggregate {
Entity Customer {
aggregateRoot
- SocialInsuranceNumber sin
String firstname
String lastname
}
String street
int postalCode
String city
}
ValueObject SocialInsuranceNumber {
String sin key
}
}
}
}


Note: In order to be able to generate contracts, an exposed Aggregate must at least contain one operation on the «Root Entity» or within a Service.

In the CML example above we used the two Bounded Contexts that we decomposed earlier in this blogpost. We have a customer management context and a self-service context that offers a web interface so that users can change their address by themselves. We further added a Context Map with a relationship between the contexts. Concretely: the self-service context needs the management context to change addresses. We added a service method changeAddress in the management context accordingly. The Aggregate that contains the service is marked as exposed with the exposedAggregates keyword in the Context Map relationship.

Based on this information we can now generate an MDSL contract in Context Mapper:

The generator produces the following simple MDSL contract:

// Generated from DDD Context Map 'blog-demo.cml' at 12.09.2020 16:04:15 CEST.
API description CustomerManagementContextAPI
usage context PUBLIC_API for BACKEND_INTEGRATION and FRONTEND_INTEGRATION

data type Address { "street":D<string>, "postalCode":D<int>, "city":D<string> }

endpoint type CustomersMainAggregate
exposes
expecting
delivering

// Generated from DDD upstream Bounded Context 'CustomerManagementContext' implementing OPEN_HOST_SERVICE (OHS) and PUBLISHED_LANGUAGE (PL).
API provider CustomerManagementContextProvider
// The customer management context is responsible for managing all the data of the insurance companies customers.
offers CustomersMainAggregate
at endpoint location "http://localhost:8001"
via protocol "RESTful HTTP"

// Generated from DDD downstream Bounded Context 'CustomerSelfServiceContext' implementing ANTICORRUPTION_LAYER (ACL).
API client CustomerSelfServiceContextClient
// The customer self-service context provides a web application where customers can change their address.
consumes CustomersMainAggregate


The contract contains one endpoint type with the changeAddress operation and one data type to represent the address. In addition, it contains one API provider that offers the endpoint and one API client that consumes it.

For more MDSL examples and an introduction into the language I refer to the MDSL website. Olaf Zimmermann’s blogpost further shows how you can use the MDSL contract to generate technology-specific contracts such as Open API Specifications (OAS); see step 7 in his tutorial.

## Wrap Up

With the generation of MDSL contracts I am done illustrating our SummerSoC 2020 paper contributions using a fictitious example model.

In this post I gave a short introduction into service decomposition with strategic Domain-driven Design (DDD), Context Mapper with its Architectural Refactorings (ARs), our proposed incremental decomposition method, and contract generation with MDSL.

Please keep in mind that it is our goal to support domain/service modelers, software architects and software engineers by offering helpful tools that ease applying strategic DDD in projects. It is not our goal to replace the human work! The tools can hopefully support you, but you still need the people knowing your domain and the experience of modelers and architects to design good domain models and API’s.

I hope you will give Context Mapper a try ;)

Stefan

## Acknowledgements

This blogpost, the SummerSoC 2020 paper, and Context Mapper would not exist without Olaf Zimmermann. Thank you very much for the great collaboration, teamwork, and all your support!

## UPDATE: SummerSoC Young Researcher Award 2020

Many thanks to the organizers of SummerSoC 2020 and ServTech for selecting me to receive the «SummerSoC Young Researcher Award 2020» 🙏

It was a pleasure to be part of this event and of course I feel honored to receive this award!

Stefan