Domain-Driven Service Design with Context Mapper and MDSL

Doc SoC
Doc SoC
Feb 20 · 11 min read
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)

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

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

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

  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

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

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)

  • 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)

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

  • 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

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?

  • 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: Architectural Decisions, (Micro-)Services and More

Stories, insights, and unsolicited advice from 25+ years of software architecting.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store