ADR-0034: Context Mapper Interchange — seams cml export Writes the Context Map as CML, and seams cml import Adds the Relationships It Can Map and Flags the Rest
Status: Accepted
Date: 2026-11-06
Source: milestone M029/S03; backlog B5; research note Domain-driven design, read against the graph; builds on ADR-0002, ADR-0017, ADR-0030 and ADR-0032
Context
ADR-0030 and ADR-0032 put bounded contexts and the relationships between them in the architecture model. Teams that do DDD often draw their context maps in Context Mapper, whose Context Mapper DSL (CML) has a ContextMap block of relationships between BoundedContexts, written with upstream and downstream role brackets:
ContextMap {
contains LocationContext, CargoBookingContext
LocationContext [U,OHS,PL]->[D,ACL] CargoBookingContext : CargoLocationRelationship
}Backlog B5 asks for a way to move a context map between the two: export the graph's map to CML so the Context Mapper tools (diagrams, refactorings) can read it, and import a map drawn there so the graph does not have to be retyped.
The two vocabularies do not match one to one. CML has roles per end (the upstream offers an open host service or a published language; the downstream conforms or builds an anti-corruption layer) and a partnership; ADR-0032 keeps one type per relationship, folds open host service into published language, and has no partnership. CML also holds much more than a context map: aggregates, entities, services, teams, use cases. The graph has its own facts for those, read from BPMN, DMN and SHACL, and a second source would contradict principle 1.
Decision
1. A context is a CML BoundedContext named by its IRI's local name
Each arch:BoundedContext in the model is exported as BoundedContext <name>, with no body, where <name> is the local name of its IRI (:expense_context becomes expense_context). ADR-0002 IRIs end in a local name that is already a valid CML identifier. The label, the domain and the owned models are not exported: CML's BoundedContext attributes (domainVisionStatement, implements, type) do not say the same thing as rdfs:label, arch:contains or seam:owns, and a guessed mapping would be a second meaning for one fact.
2. A relationship is a CML relationship with a fixed bracket form per type
Each arch:ContextRelationship is one line in the ContextMap, upstream on the left, named by the relationship IRI's local name with CML's : <name> suffix:
arch:relationshipType | CML |
|---|---|
arch:CustomerSupplier | <up> [U,S]->[D,C] <down> : <name> |
arch:Conformist | <up> [U]->[D,CF] <down> : <name> |
arch:AntiCorruptionLayer | <up> [U]->[D,ACL] <down> : <name> |
arch:PublishedLanguage | <up> [U,PL]->[D] <down> : <name> |
arch:SharedKernel | <up> [SK]<->[SK] <down> : <name> |
Chapter 11 exports as:
ContextMap {
contains expense_context, finance_context
expense_context [U]->[D,CF] finance_context : expense_to_finance
}
BoundedContext expense_context
BoundedContext finance_context- The output is sorted.
containslists the contexts by IRI; relationships follow in IRI order; theBoundedContextdeclarations follow the map in IRI order. The same model exports the same bytes. - The map has no name,
typeorstate. All three are optional in CML, and the graph has no fact for them. - A shared kernel is symmetric in CML but has an
arch:upstreamand anarch:downstreamin the graph (ADR-0032 section 3). Export writes the upstream on the left; import reads the left side as upstream.
3. Import reads the context map and maps what the table covers
seams cml import FILE reads the relationship lines of every ContextMap in a CML file and the names of its BoundedContexts. It accepts CML's arrow forms in either direction (A [U,..]->[D,..] B and B [D,..]<-[U,..] A), the symmetric <-> form, and an optional : <name> suffix and { ... } body. It decides the type from the roles, downstream role first, because the downstream role is the one that says how the downstream uses the upstream's language (ADR-0032 section 3):
ACLon the downstream end isarch:AntiCorruptionLayer;CFon the downstream end isarch:Conformist;SorCisarch:CustomerSupplier;PLon the upstream end isarch:PublishedLanguage(with or withoutOHS, which ADR-0032 folds into it);SKon both ends, or a bare<->, isarch:SharedKernel.
Each context name is resolved to the model's arch:BoundedContext whose IRI has that local name. A relationship is written as the four facts ADR-0032 defines (rdf:type, arch:upstream, arch:downstream, arch:relationshipType) in the architecture model graph that declares the upstream context. Its IRI is minted from the model's base: the CML name when the line has one, otherwise <upstream>_to_<downstream> from the two local names. So exporting chapter 11 and importing the result into a copy without :expense_to_finance gives back the same triples.
4. Import flags what it cannot map and never refuses it
Every construct with no mapping is skipped and reported on standard error as one line, seams cml import: flag <construct>: <reason>; the import still writes the rest and exits 0. The flagged constructs are:
- A relationship whose only role is
OHS, or one with no role that decides a type (a bare[U]->[D]): the graph has no type for it. - A partnership (
[P]<->[P]orPartnership): ADR-0032 has no such type. - A role the type rule did not use (the
PLin[U,PL]->[D,ACL]): the relationship keeps one type, so the other role is reported rather than silently dropped. - An unknown context name, one that matches no
arch:BoundedContextlocal name in the model, or a name two contexts share. - A relationship over a pair the model already relates with another type or the other way round: writing it would break ADR-0032's one relationship per pair.
- Everything else in the file: aggregates, entities, services, domains, teams, use cases, relationship bodies and
BoundedContextattributes. The graph has its own sources for those.
A file that does not parse is different: the import names the line, writes nothing and exits 1. Flags are for well-formed CML the graph cannot hold; a parse error is a file the import cannot read.
5. Import only adds facts, through seams_edit
The import builds one seams_edit patch (ADR-0017) of the facts missing from the architecture graph's file and applies it as seams migrate does, so the file keeps its layout and comments. It never removes a fact: a relationship in the model that the CML file does not mention stays, and a relationship that already holds the same facts adds nothing, so importing into an unchanged model leaves the file's bytes as they were. It prints added <relationship> for each relationship it writes.
Alternatives considered
- Exporting labels as
domainVisionStatementand domains asDomainblocks. Rejected: neither CML attribute means what the graph fact means, and the import would have to map them back or drop them, so the round trip would not hold. - Refusing a file with unmapped constructs. Rejected: a real Context Mapper file holds aggregates and teams as a matter of course, and a refusal would make the import useless for exactly the files it is for. Flags keep every skipped item visible.
- Import replaces the map. Rejected: a CML file drawn from part of the system would delete the relationships it does not mention. Removal stays a deliberate edit.
- Minting the IRI from the CML name only. Rejected: the name is optional in CML, and a minted
<upstream>_to_<downstream>is stable and readable when it is missing. - A CML parser library. Rejected: Context Mapper's parser is Java (Xtext), and the part the import reads is a few line forms; a small parser that names the bad line is simpler than a JVM dependency.
Consequences
seams cml export [PATH]andseams cml import FILE [PATH]are added to the CLI (M029/S03/T02, T03), with--projectand--modellikeseams storm.- A CML file's aggregates and other tactical content are never read; the DDD profile (ADR-0033) is where the graph checks the tactical rules.
- A Context Mapper user can round-trip a map: export, edit relationships in Context Mapper, import the new ones. Removals and type changes made in Context Mapper are flagged, not applied.
Open questions
- Partnership. If a later chapter needs it, it is a sixth
arch:RelationshipTypeunder ADR-0032, and this table gains[P]<->[P]. - Relationship attributes.
implementationTechnologyandexposedAggregatescould map to deployment facts and shapes; nothing reads them until a consumer needs them.