Skip to content

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:

  • DiagramsIndex parses model: / models: with nested views:, and a view ID reaches IriMinting in both diagram-oriented converters.
  • BPMN and PlantUML assert arch:Model on the model, arch:View and arch:Diagram on each view, and dct:isPartOf from view to model.
  • render names each SVG after its view.
  • ConceptCollisions fails 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 BPMNDiagram id, 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.b is reported per segment, so a.shared and c.shared both produce shared.
  • 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, modified and description attach to the view; author, version and 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.
  • render names each SVG {viewId}.svg, mirroring the index file: 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:Model and arch:View are asserted by both diagram-oriented converters, with arch:Diagram retained alongside arch:View so that queries against either class match. Structurizr continues to assert arch:Diagram only.
  • Folder position counters continue across the views of one model. Each source file is emitted separately; without this, each view restarts at schema:position 1 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 render write views under a {modelId}/ directory rather than mirroring the index file: 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.