Domain-Driven Service Design with Context Mapper and MDSL

Doc SoC (aka ZIO)
ZIO’s Blog
Published in
11 min readFeb 20, 2021
Service-Oriented Analysis and Design with DDD and MDSL (Source: [1])

This post shows how to advance from a set of user stories to OpenAPI specifications to API provider stubs via strategic and tactic DDD. This seven-step journey is partially supported and automated in two open source tools, Context Mapper and Microservice Domain-Specific Language (MDSL) Tools. Background information is available here and here.

Prerequisites: The instructions below assume that you have Context Mapper Version 6 and MDSL Tools Version 5 installed.

Step 1: Elicit Functional Requirement(s)

Write a user story in the Context Mapper DSL (CML), as explained in the Context Mapper documentation:

UserStory PaperArchiving {
As a "Researcher"
I want to create a "PaperItem"
with its "title", "authors", "venue" in a "PaperCollection"
so that "other researchers can find the referenced paper easily."
}

Make sure that the file that you create has a .cml suffix so that the Context Mapper tools can recognize it.

Step 2: Transform into Domain Analysis Model

Transform the story from Step 1 into a domain PublicationManagement containing a subdomain PaperArchive as explained here by selecting the story and then “Context Mapper: Refactor->Derive Subdomain from User Requirements” from the pop up menu. The result should look like this:

Domain PublicationManagement {
Subdomain PaperArchive {
domainVisionStatement "Aims at promoting the following benefit
for a Researcher: other researchers can find the paper easily."
Entity PaperItem {
String title
String authors
String venue
}
Entity PaperCollection {
- List<PaperItem> paperItemList
}
Service PaperArchivingService {
createPaperItem;
}
}
}

Note how incomplete the model is: all we know at this point is that some service realizes the story and that two related entities have a role to play. This analysis model captures what we know about the domain concepts from a user/domain expert point of view; it is not technical at all.

Step 3: Switch from Analysis to Design

Let’s apply some tactic and some strategic Domain-Driven Design (DDD) now, supported by a second Context Mapper transformation. Create a bounded context ReferenceManagementContext for the subdomain PaperArchive from Step 2 (select the subdomain, then “Context Mapper: Refactor->Derive Bounded Context from Subdomains”):

BoundedContext ReferenceManagementContext implements PaperArchive {
domainVisionStatement "This Bounded Context realizes the
following subdomains: PaperArchive"
type FEATURE
/* This Aggregate contains the entities and services of the
'PaperArchive' subdomain.
* TODO: You can now refactor the Aggregate.
* TODO: Add attributes and operations to the entities.
* TODO: Add operations to the services.
* Find examples and further instructions on our website:
https://contextmapper.org/docs/rapid-ooad/ */
Aggregate PaperArchiveAggregate {
Service PaperArchivingService {
String createPaperItem (@PaperItem paperItem);
}
Entity PaperItem {
String title
String authors
String venue
String paperItemId key
}
Entity PaperCollection {
String paperCollectionId key
- List<PaperItem> paperItemList
}
}
}

We have entered design mode, but still know very little about the realization (attributes, operations, etc.). Hence, the transformation converted the subdomain into a high-level bounded context of type FEATURE[1], but the generated code calls out some further TODOs.

According to this post, “a bounded context is a sub-system in a software architecture aligned to a part of your domain” . To quote the DDD crew again, “an aggregate is a lifecycle pattern originally described by Eric Evans. By aggregate, we understand a graph of objects that is a consistency boundary for our domain policies. Depending on the design of the aggregate we can either enforce them (make them invariant) or be forced to have corrective policies in place. Because of that it is important to design the boundaries of aggregates well, as they impact behaviours modelled within our domain.”

Step 4: Refine the High-Level Design

Next up is one of the few manual activities in this demo: The high-level design from Step 3 has to be detailed and improved as suggested by the TODOs. For instance:

  1. The signature of the createPaperItem operation that was created in the previous step can be improved.
  2. Additional service operations for lookup and format conversion can be introduced in the PaperArchiveAggregate.
  3. Value objects for the paperItemId and paperCollectionIdcan be specified (note that Context Mapper offers a a quick fix for this).

The result of this activity may look like this:

BoundedContext ReferenceManagementContext implements PaperArchive {
domainVisionStatement "This Bounded Context realizes the
following subdomains: PaperArchive"
type APPLICATION
/* This Aggregate contains the entities and services of the
'PaperArchive' subdomain.
* TODO: You can now refactor the Aggregate.
* [x] TODO: Add attributes and operations to the entities.
* [x] TODO: Add operations to the services.
*
*/
Aggregate PaperArchiveAggregate {
Service PaperArchivingService {
@PaperItem createPaperItem (@PaperItem paperItem);
Set<@PaperItem>lookupPapersFromAuthor(String who);
String convertToMarkdownForWebsite(@PaperItemId id);
}
Entity PaperItem {
String title
String authors
String venue
- PaperItemId paperItemId
}
ValueObject PaperItemId {
Long doi
}
Entity PaperCollection {
- PaperCollectionId paperCollectionId
- List<PaperItem> paperItemList
}
ValueObject PaperCollectionId {
Long paperCollectionId
}
}
}

The [x] mark the completed TODOs. The refinement of the design is indicated by new context type APPLICATION[1].

Step 4a (optional): Generate JHipster Application

JHipster is an application generator that receives its data entity/domain model configuration via a JHipster Domain Language (JDL) file.

This example on the Context Mapper website shows how to create JDL from design models captured in the Context Mapper DSL (please make sure that you use the latest version of the JDL template). Call the output file ReferenceManagementContext.jdl (it will be created in the src-gen folder).

A subset of the generated output is:

/* Bounded Context ReferenceManagementContext */entity PaperItem {
title String
authors String
venue String
}
entity PaperCollection {
paperCollectionId Integer
}
entity PaperItemID {
doi String
}
entity PaperCollectionId {
id String
}
microservice PaperItem, PaperCollection, PaperItemID with ReferenceManagementContext/* relationships */
relationship OneToMany {
PaperCollection{paperItemList} to PaperItem
}
relationship OneToOne {
PaperItem{paperItemId} to PaperItemID
PaperCollection{paperCollectionId} to PaperCollectionId
}

The full file ReferenceManagementContext.jdl also has two application configurations including port numbers and technology choices (you might want to switch the application from 8081 to another port, for instance 8082).

Once you generated the JDL file, you can generate the application by running JHipster:

jhipster import-jdl ReferenceManagementContext.jdl

If you follow the Context Mapper instructions from here, you end up with a running microservice architecture (including a gateway service and a service registry) that supports create, read, update, and delete operations on the entities (in MAP terms, these are exposed as Information Holder Resources), and also has a basic frontend.

The services can even be deployed to the platform-as-a-service cloud provider Heroku! General instructions can be found here and two important notes appear at the end of this post [2].

Step 5: Advance to System Contexts

The Step 3 and 4 contexts reside on a logical feature and application level of abstraction. We can now take a more physical, or integration-oriented, view on the design and create frontend and backend/service layer contexts of type SYSTEM[1]; call them ReferenceManagementFrontend and ReferenceManagementService. The third analysis and design transformation in Context Mapper does so (select the bounded context from Step 3, then “Context Mapper: Refactor->Derive Frontend and Backend Contexts”) :

We end up with a lower-level design model and a context map with an exposed aggregate, available for download here.

In in the backend service context, you might want to rename the aggregate from PaperArchiveAggregateBackend to PaperArchiveFacade (just to make the output of the next steps look a bit nicer). Use the “Rename Element” refactoring in Context Mapper to do so consistently in one step. Do the same to rename:

  • PaperItemBackend to PaperItemDTO
  • PaperItemIDBackend to PaperItemKey and (optionally) PaperCollectionIDBackend to PaperCollectionKey

We can decorate the aggregates and their operations (in entities and service) with responsibility patterns from Microservice API Patterns (MAP) if we want:

  • Add the "INFORMATION_HOLDER_RESOURCE" decorator for this pattern from MAP in front of the aggregate PaperArchiveFacade
  • Add "STATE_CREATION_OPERATION" (MAP pattern) before @PaperItemKey createPaperItem (@PaperItemDTO paperItem)
  • Add "RETRIEVAL_OPERATION" (pattern) before Set<@PaperItemDTO> lookupPapersFromAuthor (String who);)

The result could look like this:

ContextMap {
contains ReferenceManagementFrontend
contains ReferenceManagementService
ReferenceManagementService [ PL ]
-> [ CF ] ReferenceManagementFrontend {
implementationTechnology "HTTP"
exposedAggregates PaperArchiveFacade
}
}
BoundedContext ReferenceManagementService implements PaperArchive {
domainVisionStatement "This Bounded Context realizes the
following subdomains: PaperArchive"
type SYSTEM
implementationTechnology "Java, Spring Boot"
"INFORMATION_HOLDER_RESOURCE" Aggregate PaperArchiveFacade {
Service PaperArchivingService {
"STATE_CREATION_OPERATION"
@PaperItemDTO createPaperItem
(String who, String what, String where);
"RETRIEVAL_OPERATION"
Set<@PaperItemDTO>
lookupPapersFromAuthor (String who);

String convertToMarkdownForWebsite
(@PaperItemKey id);
}
Entity PaperItemDTO {
String title
String authors
String venue
- PaperItemKey paperItemId
}
Entity PaperCollectionBackend {
int paperCollectionId
- List<PaperItemDTO> paperItemList
}
ValueObject PaperItemKey {
String doi
}
ValueObject PaperCollectionKey {
String id
}
}
}

Note: Many more architectural decisions would be required now if this was a real-world design and not a tool demo. Some of the important ones are called out here.

Step 6: Generate MDSL Service Contracts (Abstract Port Level)

Microservice-Domain Specific Language (MDSL) is a service-oriented Interface Description Language (IDL) that:

  • allows describing API and service contracts but is not tightly coupled to HTTP,
  • supports incremental modeling (because it tolerates ambiguities and gaps in early draft specifications), and
  • features Microservice API Patterns (MAP) as first-class language concepts (for instance, the role and responsibility annotations we added in Step 5).

Instructions for transforming a CML model into MDSL can be found here. Note that at least one upstream context has to expose an aggregate (which must have a root entity or a service that exposes an operation). Our Step 5 model does so. The MDSL is generated into the src-gen folder of your Context Mapper project (language reference: endpoint types, data types):

API description ReferenceManagementServiceAPIdata type PaperItemDTO { "title":D<string>, "authors":D<string>, 
"venue":D<string>, "paperItemId":PaperItemKey }
data type PaperItemKey { "doi":D<string> }
data type createPaperItemParameter { "who":D<string>,
"what":D<string>, "where":D<string> }
endpoint type PaperArchiveFacade
serves as INFORMATION_HOLDER_RESOURCE
exposes
operation createPaperItem
with responsibility STATE_CREATION_OPERATION
expecting
payload createPaperItemParameter
delivering
payload PaperItemDTO
operation lookupPapersFromAuthor
with responsibility RETRIEVAL_OPERATION
expecting
payload D<string>
delivering
payload PaperItemDTO*
operation convertToMarkdownForWebsite
expecting
payload PaperItemKey
delivering
payload D<string>

One MDSL specification per bounded context is generated, whose aggregates are modeled as endpoint types. The operations are transformed into, well, operations. The complete resulting API description can be found here.

Step 7: Turn Port-Level Service Contracts into Technology Adapters

Step 7a: Convert MDSL to Open API Specification (OAS)

In the MDSL plugin, select the MDSL file from Step 6 (or open it). Choose “MDSL->Generate OpenAPI Specification” from the context menu.

A YAML file is generated (again into the src-gen folder):

openapi: 3.0.1
info:
title: ReferenceManagementServiceAPI
version: "1.0"
servers: []
tags:
- name: PaperArchiveFacade
externalDocs:
description: The role of this endpoint is
Information Holder Resource pattern
url: https://microservice-api-patterns.org/patterns/responsibility/endpointRoles/InformationHolderResource.html
paths:
/PaperArchiveFacade:
summary: general data-oriented endpoint
get:
tags:
- PaperArchiveFacade
summary: read only
description: This operation realizes the [Retrieval Operation](https://microservice-api-patterns.org/patterns/responsibility/operationResponsibilities/RetrievalOperation.html)
pattern.
operationId: lookupPapersFromAuthor
parameters:
- name: Parameter1
in: query
required: true
schema:
type: string
responses:
"200":
description: lookupPapersFromAuthor successful execution
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PaperItemDTO'
put:
tags:
- PaperArchiveFacade
summary: write only
description: This operation realizes the
[State Creation Operation](https://microservice-api-patterns.org/patterns/responsibility/operationResponsibilities/StateCreationOperation.html) pattern.
operationId: createPaperItem
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PaperItemDTO'
responses:
"200":
description: createPaperItem successful execution
content:
application/json:
schema:
$ref: '#/components/schemas/PaperItemKey'
post:
tags:
- PaperArchiveFacade
description: unspecified operation responsibility
operationId: convertToMarkdownForWebsite
parameters: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PaperItemKey'
responses:
"200":
description: convertToMarkdownForWebsite
successful execution
content:
application/json:
schema:
type: object
properties:
anonymous1:
type: string
components:
schemas:
PaperItemDTO:
type: object
properties:
title:
type: string
authors:
type: string
venue:
type: string
paperitemId:
$ref: '#/components/schemas/PaperItemKey'
PaperItemKey:
type: object
properties:
id:
type: string

Load this OAS file into an Open API tool, for instance the online Swagger editor, to see whether this platform-specific API description and technical service contract validates.

If you are unhappy with the default output, work with the HTTP binding section in the provider part of MDSL to improve the endpoint-to-resource and operation-to-verb mappings according to the principles of REST. Run the MDSL-to-Open API generator again to pick up the new binding; it is documented here.

Step 7b (optional): Generate Other IDLs and Java Modulith

MDSL 5 supports several other output formats (following the principle of “polyglot integration”):

  • gRPC Protocols Buffers
  • GraphQL schema language
  • Jolie, which in turn yields WSDL and XML Schema
  • Plain Old Java Objects (POJO) “modulith” including client server stubs and unit tests ready to be executed (with some very simple sample data)

These generators are invoked in the same ways as the OAS one. The MDSL Tools: Users Guide provides usage instructions and mapping information.

Wrap Up and Recap

Congratulations! We have turned a requirement into concrete service contracts in different technologies now, and are only one step away from an early, layered architectural prototype now (this step is featured in this post). The demo files can be downloaded here.

Here are some questions you might be able to answer once you have run though the above steps:

  • How did the MDSL facade endpoint get mapped to Open API, Protocols Buffers, GraphQL schema, Jolie, Java?
  • How were the service operations with their request and response messages mapped to HTTP verbs and JSON (and why)? What do the other generators do with them?
  • Where do the MAP pattern links on endpoint and operation level (in the OpenAPI specification) come from?
  • What happened to the data types used in request and response messages?
  • Is there anything else worth mentioning (or missing)?

This post demonstrated how to progress from requirement elicitation/analysis to domain-driven design and contract-first service design in a few incremental steps, most of which tool supported (Context Mapper, MDSL editor). The hard work that will (and, imho, should) remain a human activity is the manual design Step 4 (and the optional MAP decoration of the DDD/CML output). The transformations and code generation steps demonstrate and educate us about the required steps and automate some of the repetitive boring tasks (“glue coding”, “plumbing” — so that you can focus on the design and, eventually, implementation work on the business logic/domain layer (rather than integration coding).[3]

Really Done?

Well, not quite, the seven steps only yielded an early prototype not doing that much. Next up are:

  • Write client and server stubs, or let tools such as Swagger (or API-First in JHipster) generate them.
  • Implement real business logic, for instance to connect the service layer facade with the persistence and data access layer in the JHipster backend.
  • Write your own frontend that replaces the JHipster one.
  • Integrate via message-oriented middleware instead of HTTP.
  • Apply more microservice infrastructure patterns such as API Gateway or load balancers and deploy to a cloud.
  • Go back to analysis and design: iterate over the design; add Team context(s), define more user stories (or use cases) and owners of system BCs, and/or refactor the CML model continuously. For instance, apply tactic DDD patterns such as Repository, Factory, Service in addition to Aggregate, Entity and Value Object to craft a rich Domain Model.

The list goes on. Service and application design, implementation and integration might be business as usual, but never gets boring! The Design Practice Repository (DPR) can guide you through the more advanced tasks.

Comments? Suggestions? Need help? Contact me!

Olaf (a.k.a. socadk)

An extended version of this post is available on my personal blog.

[1] The transformation steps and the FAST context types are introduced in: Kapferer, S.; Zimmermann, O.: Domain-Driven Architecture Modeling and Rapid Prototyping with Context Mapper, selected extended papers from Model-Driven Engineering and Software Development, Springer CCIS Volume 1361 (PDF).

[2] Since the application receives a URI that has to be globally unique, chances are that you have to change the application name in the JDL file; Heroku will not be able to build and launch the application in case of name conflicts. Also: the default application configuration in the above JDL only works with validated Heroku accounts; you might want to add prodDatabaseType postgresqlto the JDL application definition.

[3] You might not want use such tool chain in everyday forward engineering all the time, but hopefully you find it educational and can make good use of it in prototyping and when mocking or trying out service designs.

© Olaf Zimmermann, 2020. All rights reserved.

--

--

ZIO’s Blog
ZIO’s Blog

Published in ZIO’s Blog

The posts in this blog take three different views on software architecture: practicing architect, pattern enthusiast, and software engineering researcher.

Doc SoC (aka ZIO)
Doc SoC (aka ZIO)

Written by Doc SoC (aka ZIO)

Architectural Decision Maker, Domain-Driven Designer, Serviceorienteer. Co-author of "Patterns for API Design", "Design Practice Reference", Y-Statements, MADR