ADR 0002 — Models hold views: index granularity for BPMN and PlantUML¶
Status: Accepted, implemented
Related: ADR 0001 decides where a declared identity is maintained; this ADR decides which resource that identity names.
Implementation summary:
DiagramsIndexparsesmodel:/models:with nestedviews:, and a view ID reachesIriMintingin both diagram-oriented converters.- BPMN and PlantUML assert
arch:Modelon the model,arch:Viewandarch:Diagramon each view, anddct:isPartOffrom view to model. rendernames each SVG after its view.ConceptCollisionsfails a run in which two views declare the same element or relationship ID with different names or types.- The flat
diagrams:schema retains its behaviour, including its provenance subjects.
Context¶
In the core ontology a diagram is an arch:View (or arch:Diagram) inside an arch:Model,
alongside the elements and relationships it depicts. LinkedArchiVocab carries both classes
and they are distinct.
The flat diagram index cannot express that relationship. It has a single ID field,
diagrams[].id, which becomes a model ID because the converters resolve one model per input
file. For BPMN and PlantUML, where a source file holds one diagram, each diagram therefore
becomes its own model rather than a view of one.
Which resources that field names differs by notation:
- BPMN takes the view ID from the source file (the
BPMNDiagramid, or the depicted process), so model and view have distinct IDs, but the model contains one diagram. - PlantUML has no diagram ID in the source, so the model ID is reused as the view ID:
…/plantuml/order-flow/view/order-flow. One value names two resources.
Emitted structure per converter:
| Converter | Views per source file | Model resource | View resource |
|---|---|---|---|
| ArchiMate | many (<view> each) |
typed arch:Model |
typed arch:View, id = Exchange identifier |
| Structurizr | many (one per view key) |
not typed | typed arch:Diagram, id = view key |
| BPMN | one per BPMNDiagram |
not typed | typed arch:Diagram, remapped onto the depicted Process IRI |
| PlantUML | exactly one | not typed | typed arch:Diagram, id = the model ID |
| Backstage | none (catalog data) | not typed | — |
ArchiMate and Structurizr already have the right shape, because their source format carries several views per file. Only the diagram-oriented converters are affected.
Consequences of the current granularity¶
The same element in two diagrams becomes two resources. Two .puml files that both
declare class OrderService, converted in one run with an index listing both, produce this
(real output, reformatted):
<https://example.org/la/plantuml/order-flow/graph/semantic> {
<https://example.org/la/plantuml/order-flow/element/OrderService>
a arch:Element , arch:ModelConcept , uml:Class ;
skos:notation "OrderService" ;
skos:prefLabel "OrderService"@en .
<https://example.org/la/plantuml/order-flow/view/order-flow>
a arch:Diagram ;
skos:prefLabel "[Order Flow]"@en . # view IRI repeats the model ID
}
<https://example.org/la/plantuml/fulfilment-flow/graph/semantic> {
<https://example.org/la/plantuml/fulfilment-flow/element/OrderService>
a arch:Element , arch:ModelConcept , uml:Class ;
skos:notation "OrderService" ;
skos:prefLabel "OrderService"@en . # same label, different resource
<https://example.org/la/plantuml/fulfilment-flow/view/fulfilment-flow>
a arch:Diagram ;
skos:prefLabel "[Fulfilment Flow]"@en .
}
Two IRIs with identical labels and types, and no assertion that they denote the same component. This is the principal defect: the graph's purpose is to represent one element depicted by several views.
Named graphs are fragmented per source file. The run above produces six named graphs,
order-flow/graph/{semantic,views,provenance} and fulfilment-flow/graph/{…}, so a query
covering both diagrams must union graphs related only by naming convention.
Model-level metadata is per diagram. title, author and version describe one diagram, so
a model spanning several diagrams repeats its metadata once per diagram, with no resource
representing the model.
Options considered¶
A. Retain the current granularity and document it¶
No implementation cost. The defects above persist, and a consumer requiring element identity
across diagrams must assert it externally, by owl:sameAs or by matching labels.
B. Declare the model and its views in the index¶
The index gains an explicit level. Both forms below describe the two diagrams used in the previous section; the first is the index that produced the output shown there.
# flat schema — one entry per file, each entry a model
diagrams:
- id: order-flow
file: order-flow.puml
status: publish
- id: fulfilment-flow
file: fulfilment-flow.puml
status: publish
# proposed — the model is named once, and lists its views
model:
id: order-domain
title: "Order Domain"
author: "Commerce Team"
views:
- id: order-flow
file: order-flow.puml
status: publish
- id: fulfilment-flow
file: fulfilment-flow.puml
status: publish
Several models can live in one index:
models:
- id: order-domain
title: "Order Domain"
views:
- id: order-flow
file: processes/order-flow.puml
status: publish
- id: fulfilment-flow
file: processes/fulfilment-flow.puml
- id: payments
title: "Payments"
views:
- id: payment-authorisation
file: payments/payment-auth.puml
status: publish
Each field names exactly one resource:
| Flat schema | Grouped schema | Names |
|---|---|---|
diagrams[].id |
model.id / models[].id |
the arch:Model and its named graphs |
| not expressible; taken from the source file, or the model ID reused | views[].id |
the arch:View, and the render SVG filename |
diagrams[].file |
views[].file |
the source file for that view |
diagrams[].status |
views[].status |
the lifecycle state, per view |
diagrams[].title, author, version |
model level, view level, or both | see "Consequent rules" |
Every view of a model converts into that model's namespace and its named graphs.
The same two source files under the grouped schema produce one model with two views and one
OrderService element:
<https://example.org/la/plantuml/order-domain/graph/model> {
<https://example.org/la/plantuml/order-domain>
a arch:Model ;
skos:prefLabel "Order Domain"@en .
}
<https://example.org/la/plantuml/order-domain/graph/semantic> {
# one element, depicted by both views. `arch:inModel` is what puts it in the model —
# the named graph does not, and could not once the graph is split per input file.
<https://example.org/la/plantuml/order-domain/element/OrderService>
a arch:Element , arch:ModelConcept , uml:Class ;
arch:inModel <https://example.org/la/plantuml/order-domain> ;
skos:prefLabel "OrderService"@en .
<https://example.org/la/plantuml/order-domain/element/PaymentService> a arch:Element .
<https://example.org/la/plantuml/order-domain/element/ShippingService> a arch:Element .
<https://example.org/la/plantuml/order-domain/view/order-flow>
a arch:View , arch:ModelConcept ;
arch:inModel <https://example.org/la/plantuml/order-domain> ;
skos:prefLabel "[Order Flow]"@en .
<https://example.org/la/plantuml/order-domain/view/fulfilment-flow>
a arch:View , arch:ModelConcept ;
arch:inModel <https://example.org/la/plantuml/order-domain> ;
skos:prefLabel "[Fulfilment Flow]"@en .
}
<https://example.org/la/plantuml/order-domain/graph/views> {
# the same element appears as a node in each view
<https://example.org/la/plantuml/order-domain/view/order-flow/node/OrderService>
a archvis:ArchNode ;
archvis:view <https://example.org/la/plantuml/order-domain/view/order-flow> ;
archvis:archElement <https://example.org/la/plantuml/order-domain/element/OrderService> .
<https://example.org/la/plantuml/order-domain/view/fulfilment-flow/node/OrderService>
a archvis:ArchNode ;
archvis:view <https://example.org/la/plantuml/order-domain/view/fulfilment-flow> ;
archvis:archElement <https://example.org/la/plantuml/order-domain/element/OrderService> .
}
<https://example.org/la/plantuml/order-domain/graph/provenance> {
# source moves to the view; the model carries aggregate metadata only
<https://example.org/la/plantuml/order-domain>
dct:creator "Commerce Team" ; owl:versionInfo "1.0" .
<https://example.org/la/plantuml/order-domain/view/order-flow>
dct:source "order-flow.puml" .
<https://example.org/la/plantuml/order-domain/view/fulfilment-flow>
dct:source "fulfilment-flow.puml" .
}
One model's named graphs rather than two models' worth, one OrderService rather than two, and archvis:archElement
from both views resolving to it, so the views depicting a given element are derivable from a
single query.
C. Infer the model from the directory layout¶
Derive the model from the directory containing the source files, for example
models/order-domain/*.bpmn. This requires no schema change, but makes identity a consequence of
file location: moving a file re-parents its elements without any declaration changing.
Decision¶
Adopt option B, as an opt-in schema, with element-merge semantics stated and validated.
The flat diagrams: schema retains its meaning of one model per entry, which is correct for a
repository of unrelated diagrams. The grouped schema applies where several diagrams describe one
model.
Element identity across views¶
When two source files share a model namespace, an identical element ID in both denotes one
resource. This is the intended behaviour: an OrderService element drawn in three diagrams is
one arch:Element referenced by three archvis:ArchNodes.
The same mechanism can merge unrelated elements, and the risk differs by notation:
- PlantUML derives element IDs from the code an author refers to the entity by
(ADR 0010), which the notation keeps unique within a diagram,
so a merge across views normally reflects a genuine match. Two views may still use one code for
different things, and a nested
namespace a.bis reported per segment, soa.sharedandc.sharedboth produceshared. - BPMN IDs are tool-assigned and unique only within a file. Two hand-written or templated
processes may each contain
Task_1, which would merge two unrelated tasks.
Grouping therefore requires a collision check. Where two views of one model contribute the same element IRI with a different label or a different type, the conversion must fail rather than union the triples. The same IRI with a consistent label and type is a valid merge and passes without a message.
Without that check, two files each containing Task_1 would produce one resource carrying both
identities and no error:
<https://example.org/la/bpmn/order-domain/graph/semantic> {
<https://example.org/la/bpmn/order-domain/element/Task_1>
a bpmn:UserTask , bpmn:ServiceTask , # ← two types, from two files
arch:Element , arch:ModelConcept ;
bpmn:name "Validate Order" , "Charge Card" . # ← two names
}
Consequent rules¶
- Provenance is recorded at the level it describes.
dct:source,dct:title,created,modifiedanddescriptionattach to the view;author,versionand the model title attach to the model. Under the flat schema no view is declared, so all of them attach to the model, as before. rendernames each SVG{viewId}.svg, mirroring the indexfile:sub-directory. Where no view ID is declared the model ID is used, so flat-schema output paths are unchanged. Element hyperlinks address{model}/element/{id}, since elements belong to the model.- A declared view ID takes precedence over the BPMN process-as-view remap. With a view ID the
view is a distinct resource at
/view/{id}; without one the remap applies, leaving existing output unchanged. arch:Modelandarch:Vieware asserted by both diagram-oriented converters, witharch:Diagramretained alongsidearch:Viewso that queries against either class match. Structurizr continues to assertarch:Diagramonly.- Folder position counters continue across the views of one model. Each source file is emitted
separately; without this, each view restarts at
schema:position1 and one folder contains several members with the same position. - Backstage is out of scope. A catalog file is not a diagram and has no views.
- ArchiMate and Structurizr require no change to their emitted structure. Both would benefit from index support, which they do not have.
Schema detection¶
DiagramsIndex.parse accepts models: as a synonym for diagrams:, denoting a flat list of
per-file entries. The grouped schema requires the same key with different semantics.
The two are distinguished by structure rather than by a version marker: models: is read as
grouped only when an entry contains views:. A flat list under either key retains its meaning,
and no existing index requires a declaration. A model: object without views: is rejected,
since a model with no diagrams would convert nothing.
Consequences¶
- An element depicted by several diagrams is one resource, as the ontology describes.
- A model spanning several source files has one set of named graphs and one location for its model-level metadata.
- Verifying model ID uniqueness across repositories remains the aggregation repository's responsibility, since that is where artifacts from all sources are merged. A converter emits model and view structure for the sources it is given.
- Adopting the grouped schema for an existing index changes its model IDs, and therefore every IRI in those models and the name of every rendered SVG. Identity is not yet aliasable (ADR 0001, out of scope), so the change is only safe for models whose IRIs are not yet published or whose consumers can be updated at the same time.
Decisions taken during implementation¶
- A declared view ID names the view resource, not only the SVG file, and takes precedence over the BPMN process-as-view remap.
- View IDs are unique per model, consistent with element IDs, and validated as such.
- Metadata is split by level: view fields describe the diagram, model fields describe the model
and are inherited by views that do not declare their own. For BPMN, in-file
dc:/dcterms:values supply the view's metadata and index values take precedence over them. - The collision check fails the run rather than emitting a warning, consistent with the remaining index validation.
Open questions¶
- Should
renderwrite views under a{modelId}/directory rather than mirroring the indexfile:layout? Mirroring keeps existing output paths stable; grouping by model collects a model's SVGs in one directory. - The collision check compares name and type, so two distinct elements sharing both still merge without a message. A stricter comparison, for example of incoming relationships, would narrow this.
- ArchiMate and Structurizr have no index support. Grouping is not required for them, but
status:filtering and index metadata would apply.