Data Models

Overview

Bausteinsicht works with three data formats that must be kept in sync:

Format Purpose Location

JSON Model (JSONC)

Single source of truth for the architecture

*.jsonc files in project directory

JSON Schema

Validation and IDE support for the model

Published schema file

draw.io XML (mxGraph)

Visual representation of architecture views

*.drawio files in project directory (uncompressed XML)

Entity Model

The JSON model is a tree of entities rooted at BausteinsichtModel. The diagram shows composition (filled diamond — the parent owns the child) and references (dashed arrow — one entity refers to another by ID). Cardinalities use UML notation.

model entity model

The element ID (the key in the model map, dot-pathed for nested children) is the model’s primary key: relationships, view scope, and dynamic-view steps reference elements by that ID, while a view’s include/exclude match element IDs by literal value or wildcard pattern (e.g. webshop., *). The kind, decisions, and tag references must resolve to entries defined in the specification. These referential constraints are enforced at validation time — see Validation Rules.

JSON Model Structure

The model has these top-level sections. specification, model, relationships, and views are the core; the rest are optional: $schema (points the IDE at the JSON Schema for validation and autocompletion), config (project metadata/legend), meta (e.g. staleDetection settings), dynamicViews (sequence diagrams), constraints (lint rules), and asIs/toBe (for the diff command).

{
  "$schema": "https://raw.githubusercontent.com/docToolchain/Bausteinsicht/main/schemas/bausteinsicht.schema.json",

  // Define available element and relationship kinds
  "specification": {
    "elements": {
      "actor": {
        "notation": "Actor",
        "description": "A person or external system that interacts with the system"
      },
      "system": {
        "notation": "Software System",
        "description": "A top-level software system",
        "container": true
      },
      "container": {
        "notation": "Container",
        "description": "A deployable unit within a system",
        "container": true
      },
      "component": {
        "notation": "Component",
        "description": "A logical grouping within a container",
        "container": true
      }
    },
    "relationships": {
      "uses": { "notation": "uses" },
      "async": { "notation": "async", "dashed": true }
    }
  },

  // The actual architecture elements and their hierarchy
  "model": {
    "customer": {
      "kind": "actor",
      "title": "Customer",
      "description": "End user of the webshop"
    },
    "webshop": {
      "kind": "system",
      "title": "Webshop",
      "description": "Online shopping platform",
      "children": {
        "api": {
          "kind": "container",
          "title": "REST API",
          "technology": "Spring Boot",
          "description": "Handles all HTTP requests",
          "children": {
            "auth": {
              "kind": "component",
              "title": "Auth Module",
              "technology": "Spring Security"
            }
          }
        },
        "db": {
          "kind": "container",
          "title": "Database",
          "technology": "PostgreSQL",
          "description": "Stores all persistent data"
        }
      }
    }
  },

  // Connections between elements
  "relationships": [
    {
      "from": "customer",
      "to": "webshop.api",
      "label": "uses",
      "kind": "uses",
      "description": "Sends HTTP requests"
    },
    {
      "from": "webshop.api",
      "to": "webshop.db",
      "label": "reads/writes",
      "kind": "uses"
    }
  ],

  // View definitions
  "views": {
    "context": {
      "title": "System Context",
      "include": ["customer", "webshop"],
      "description": "High-level system overview"
    },
    "containers": {
      "title": "Container View",
      "scope": "webshop",
      "include": ["customer", "webshop.*"],
      "description": "Webshop internals"
    }
  },

  // Dynamic views: sequence diagrams describing runtime behaviour
  "dynamicViews": [
    {
      "key": "login-flow",
      "title": "Login Flow",
      "description": "Customer logs in to the webshop",
      "steps": [
        { "index": 1, "from": "customer",    "to": "webshop.api", "label": "POST /login",  "type": "sync" },
        { "index": 2, "from": "webshop.api", "to": "webshop.db",  "label": "findUser()",   "type": "sync" },
        { "index": 3, "from": "webshop.db",  "to": "webshop.api", "label": "user record",  "type": "return" },
        { "index": 4, "from": "webshop.api", "to": "customer",    "label": "200 OK + JWT", "type": "return" }
      ]
    }
  ]
}

Element Properties

Property Type Required Description

kind

string

yes

Element kind as defined in specification

title

string

yes

Display name shown in diagrams

description

string

no

Detailed description (shown in tooltips)

technology

string

no

Technology/framework used

tags

string[]

no

Tags for filtering and styling

status

string

no

Lifecycle status: proposed, design, implementation, deployed, deprecated, or archived (see Element Lifecycle)

decisions

string[]

no

ADR IDs that govern this element

lastModified

string

no

RFC3339 timestamp; overrides git-derived last-modified for stale detection

children

object

no

Nested child elements (only if kind has container: true)

metadata

object

no

Arbitrary key-value metadata

The key of each element in the model object (e.g., "customer", "api") serves as its unique ID. Nested elements are referenced using dot notation: "webshop.api.auth".

Element Lifecycle

An element’s optional status follows this intended progression (the status command groups elements by it):

element lifecycle

The transitions themselves are not enforced — status is a plain string — but validation does check the following:

  • An unknown status value (not one of the six above) produces a warning listing the valid values.

  • An archived element should have no outgoing relationships (archived elements are no longer active).

  • A deprecated element should have an outgoing relationship to a deployed successor of the same kind (a migration target); otherwise a warning is emitted.

Relationship Properties

Property Type Required Description

from

string

yes

Source element ID (dot notation for nested)

to

string

yes

Target element ID (dot notation for nested)

label

string

no

Display label on the connector

kind

string

no

Relationship kind as defined in specification

description

string

no

Detailed description

decisions

string[]

no

ADR IDs that govern this relationship

cardinality

string

no

One of 1:1, 1:N, N:N

dataFlow

string

no

One of sync, async, request/response, publish/subscribe

View Properties

Property Type Required Description

title

string

yes

View title displayed as tab/page name

scope

string

no

Parent element whose children are shown

include

string[]

no

Element IDs to include (supports wildcard patterns, see [Wildcard Patterns])

exclude

string[]

no

Element IDs to exclude from the view (supports wildcard patterns, see [Wildcard Patterns])

filter-tags

string[]

no

Include only elements that carry all of these tags

exclude-tags

string[]

no

Exclude elements that carry any of these tags

description

string

no

View description

layout

string

no

Auto-layout mode for new pages: "layered" (default), "grid", or "none". Layered groups elements by kind tier; grid places alphabetically in a grid; none uses a horizontal row.

[[Wildcard Patterns]] ==== Wildcard Patterns

The include and exclude arrays support the following patterns for matching elements:

Pattern Description

"id"

Exact match — includes only the element with the given ID

"*"

All top-level elements only (IDs without dots)

"**"

All elements at all levels (entire model)

"prefix.*"

Direct children of prefix (one level deep, e.g., "webshop.*" matches webshop.api and webshop.db but not webshop.api.auth)

"prefix.**"

All descendants of prefix (recursive, e.g., "webshop.**" matches webshop.api, webshop.db, and webshop.api.auth)

Dynamic View Properties

Dynamic views are defined as a top-level dynamicViews array. Each entry describes a sequence diagram for one runtime scenario.

Property Type Required Description

key

string

yes

Unique identifier for the view (used as filename and CLI filter key)

title

string

yes

Displayed heading in the rendered diagram

description

string

no

Optional prose description of the scenario

steps

SequenceStep[]

yes

Ordered list of message steps (at least one required)

Sequence Step Properties

Property Type Required Description

index

integer

yes

Sort order of the step (must be unique within a view, starts at 1)

from

string

yes

Sender element ID (must exist in the flat model)

to

string

yes

Receiver element ID (must exist in the flat model)

label

string

yes

Message label displayed on the arrow

type

string

no

Arrow style: sync (default), async, or return

JSONC Comment Support

Model files use the JSONC (JSON with Comments) format. Both comment styles are supported:

  • // — single-line comments

  • /\* …​ */ — block comments (can span multiple lines)

Validation Rules

bausteinsicht validate enforces the model’s integrity constraints. The complete, code-traceable catalogue — each rule’s exact statement, severity, and source function — is in the Business Rules section of 01_use_cases (BR-001–BR-033). The constraints on the data model group as follows:

  • Structural (errors): required fields — kind/title on elements, title on views, key/title on dynamic views, and at least one step per dynamic view (BR-001, BR-004, BR-013, BR-018, BR-019, BR-020); known kinds (BR-002, BR-009, BR-024, BR-025); container nesting and depth limit (BR-003, BR-006); non-empty element IDs (BR-005).

  • Referential integrity (errors): relationship from/to, view scope, non-wildcard include/exclude, filter-tags/exclude-tags, dynamic-view step from/to, and decisions references all resolve into the model or specification (BR-007, BR-008, BR-015, BR-016, BR-017, BR-021, BR-026).

  • Enumerations (errors): cardinality, dataFlow, view layout, and step type take only their fixed defined values (BR-010, BR-011, BR-014, BR-022).

  • Uniqueness (errors): no fully-duplicate relationships; unique step indices (BR-012, BR-023).

  • Lifecycle & advisory (warnings): valid status, archived/deprecated lifecycle rules, orphan and superseded decision references, and empty-model checks (BR-027–BR-033).

Errors abort the command (exit code 1); warnings are reported with a WARNING: prefix and are non-blocking (exit code 0).

draw.io XML Structure

Bausteinsicht generates and reads uncompressed draw.io XML (compressed="false") for Git-friendliness.

File Structure

<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="bausteinsicht" compressed="false">

  <!-- One <diagram> per view -->
  <diagram id="view-context" name="System Context">
    <mxGraphModel dx="1422" dy="794" grid="1" gridSize="10"
                  page="1" pageWidth="1169" pageHeight="827"
                  background="#ffffff">
      <root>
        <!-- Mandatory base cells (always present) -->
        <mxCell id="0" />
        <mxCell id="1" parent="0" />

        <!-- All diagram content goes here -->
      </root>
    </mxGraphModel>
  </diagram>

</mxfile>

Mandatory Base Cells

Every page requires exactly two base cells:

  • id="0" — root cell, no parent

  • id="1" parent="0" — default drawing layer, parent for all top-level content

These are never modified by Bausteinsicht.

Elements as <object> with Custom Properties

Every Bausteinsicht element is wrapped in an <object> element to carry custom metadata. The nested <mxCell> has no id — the <object> owns the ID. The parent <mxCell> uses container=1 so that child text sub-cells are grouped inside it.

<object label=""
        id="containers--webshop.api"
        bausteinsicht_id="webshop.api"
        bausteinsicht_kind="container"
        technology="Spring Boot"
        tooltip="Handles all HTTP requests">
  <mxCell style="rounded=1;whiteSpace=wrap;html=1;
                 fillColor=#438DD5;strokeColor=#3C7FC0;fontSize=13;container=1;"
          vertex="1"
          parent="1">
    <mxGeometry x="200" y="150" width="240" height="150" as="geometry" />
  </mxCell>
</object>

<!-- Text sub-cells: separate mxCell children for title, technology, description -->
<mxCell id="containers--webshop.api-title" value="REST API"
        style="text;html=1;fontSize=14;fontStyle=1;fontColor=#ffffff;fillColor=none;strokeColor=none;align=center;verticalAlign=middle;movable=0;resizable=0;deletable=0;editable=0;"
        vertex="1" parent="containers--webshop.api">
  <mxGeometry x="0" y="20" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="containers--webshop.api-tech" value="[Spring Boot]"
        style="text;html=1;fontSize=11;fontStyle=2;fontColor=#CCCCCC;fillColor=none;strokeColor=none;align=center;verticalAlign=middle;movable=0;resizable=0;deletable=0;editable=0;"
        vertex="1" parent="containers--webshop.api">
  <mxGeometry x="0" y="55" width="240" height="20" as="geometry" />
</mxCell>
<mxCell id="containers--webshop.api-desc" value="Handles all HTTP requests"
        style="text;html=1;fontSize=10;fontColor=#BBBBBB;fillColor=none;strokeColor=none;align=center;verticalAlign=middle;movable=0;resizable=0;deletable=0;editable=0;"
        vertex="1" parent="containers--webshop.api">
  <mxGeometry x="0" y="80" width="240" height="40" as="geometry" />
</mxCell>

Custom properties on <object>:

Property Purpose

label

Empty string ("") for elements using sub-cells. Legacy elements may still have an HTML label (backward compatible).

id

Cell ID — page-scoped for file-wide uniqueness: <viewID>--<elementID> (e.g., context—​customer). When an element appears on multiple view pages, each instance has a distinct cell ID.

bausteinsicht_id

The canonical element ID from the model (e.g., customer). Used for reverse sync identification. Unlike id, this value is the same across all pages where the element appears.

bausteinsicht_kind

Element kind (actor, system, container, component)

technology

Technology string from the model

tooltip

Element description — shown on hover in draw.io

link

Drill-down navigation link (see Navigation section)

Text Sub-Cells (Grouped Labels)

Each element’s display text is decomposed into independent child <mxCell> elements, giving full control over font size, color, weight, and vertical positioning. The parent element has label="" and container=1.

Three child cell types exist, identified by their ID suffix:

Sub-Cell ID Suffix Style

Title

-title

Bold, larger font (14px), element’s font color

Technology

-tech

Italic, smaller font (11px), [bracketed], lighter color

Description

-desc

Smallest font (10px), lighter color

Child cells use movable=0;resizable=0;deletable=0;editable=0; to prevent accidental manipulation in draw.io.

Technology and description sub-cells are only created when the corresponding field is non-empty. Actor elements have only a title sub-cell.

HTML Labels (Backward Compatible)

Legacy draw.io files may still use HTML-formatted label attributes instead of sub-cells. Bausteinsicht reads both formats: it checks for child text sub-cells first, and falls back to parsing the HTML label if none are found.

The legacy label format has up to three lines:

  1. Title — bold (<b>Title</b>)

  2. Technology — grey, wrapped in square brackets, italic

  3. Description — lighter grey, smaller font

Entity escaping rules for HTML labels:

Character Escaped

<

<

>

>

"

"

&

&

Containers (Nested Elements)

Systems and containers that hold children use the swimlane style. Child elements set parent to the container’s ID. Child coordinates are relative to the container’s top-left corner (below the header).

<!-- Container (system boundary) -->
<object label="Webshop" id="webshop"
        bausteinsicht_id="webshop"
        bausteinsicht_kind="system">
  <mxCell style="swimlane;startSize=30;fillColor=#f5f5f5;
                 strokeColor=#666666;fontColor=#333333;
                 whiteSpace=wrap;html=1;rounded=1;container=1;"
          vertex="1" parent="1">
    <mxGeometry x="100" y="100" width="500" height="300" as="geometry" />
  </mxCell>
</object>

<!-- Child element — parent references the container -->
<object label=""
        id="containers--webshop.api"
        bausteinsicht_id="webshop.api"
        bausteinsicht_kind="container"
        technology="Spring Boot">
  <mxCell style="rounded=1;whiteSpace=wrap;html=1;
                 fillColor=#438DD5;strokeColor=#3C7FC0;container=1;"
          vertex="1"
          parent="webshop">
    <mxGeometry x="30" y="60" width="240" height="150" as="geometry" />
  </mxCell>
</object>
<!-- Sub-cells for the child element (parent = child's cell ID) -->
<mxCell id="containers--webshop.api-title" value="REST API"
        style="text;html=1;fontSize=14;fontStyle=1;..." vertex="1"
        parent="containers--webshop.api">
  <mxGeometry x="0" y="20" width="240" height="30" as="geometry" />
</mxCell>

Connectors (Relationships)

Connectors use edge="1" and reference source/target by their page-scoped cell IDs. Connectors always use parent="1" (the layer), even when connecting shapes inside containers.

<mxCell id="rel-containers--customer-containers--webshop.api-0"
        value="uses"
        style="edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;
               endArrow=block;endFill=1;strokeColor=#666666;"
        edge="1"
        source="containers--customer"
        target="containers--webshop.api"
        parent="1">
  <mxGeometry relative="1" as="geometry" />
</mxCell>

Connection point strategy:

By omitting exitX/exitY/entryX/entryY style properties, draw.io auto-routes connectors from the nearest perimeter point. This ensures connectors look correct when elements are moved by the user.

Connector ID convention: rel-<scopedSource>-<scopedTarget>-<index> where scoped IDs use the <viewID>--<elementID> format and <index> is a zero-based integer for disambiguation when multiple relationships exist between the same pair (e.g., rel-containers—​customer-containers—​webshop.api-0).

Cross-Page Navigation (Drill-Down)

Elements link to detail views using the link attribute on <object>:

<!-- System element with link to container view -->
<object label="Webshop" id="webshop"
        bausteinsicht_id="webshop"
        bausteinsicht_kind="system"
        link="data:page/id,view-containers">
  <mxCell style="..." vertex="1" parent="1">
    <mxGeometry ... />
  </mxCell>
</object>

Link format: data:page/id,<diagram-id> where <diagram-id> matches the id attribute of the target <diagram> element.

Back-navigation buttons are generated in detail views:

<object label="&amp;larr; System Context" id="nav-back-context"
        link="data:page/id,view-context">
  <mxCell style="rounded=1;fillColor=#f8cecc;strokeColor=#b85450;html=1;fontSize=10;"
          vertex="1" parent="1">
    <mxGeometry x="20" y="20" width="140" height="30" as="geometry" />
  </mxCell>
</object>

Multi-Page Structure

Each view is a separate <diagram> element. The id attribute uses the format view-<viewKey> (e.g., view-context for a view with key "context"). The name attribute is the view title displayed as the tab label.

<mxfile host="bausteinsicht" compressed="false">
  <diagram id="view-context" name="System Context">
    <mxGraphModel ...>...</mxGraphModel>
  </diagram>
  <diagram id="view-containers" name="Container View">
    <mxGraphModel ...>...</mxGraphModel>
  </diagram>
</mxfile>

Base cells (id="0" and id="1") exist on every page with the same IDs — this is standard draw.io convention. Element cell IDs are page-scoped using <viewID>--<elementID> to ensure file-wide uniqueness (e.g., context—​customer and containers—​customer for the same element on different pages). The bausteinsicht_id attribute always holds the canonical element ID.

Go Struct Mappings

// Top-level model file
type BausteinsichtModel struct {
    Schema        string                 `json:"$schema,omitempty"`
    Config        Config                 `json:"config,omitempty"`
    Meta          map[string]interface{} `json:"meta,omitempty"`
    Specification Specification          `json:"specification"`
    Model         map[string]Element     `json:"model"`
    Relationships []Relationship         `json:"relationships"`
    Views         map[string]View        `json:"views"`
    DynamicViews  []DynamicView          `json:"dynamicViews,omitempty"`
    Constraints   []Constraint           `json:"constraints,omitempty"`
    AsIs          *ModelSnapshot         `json:"asIs,omitempty"`
    ToBe          *ModelSnapshot         `json:"toBe,omitempty"`
}

// Specification defines available element and relationship kinds
type Specification struct {
    Elements      map[string]ElementKind       `json:"elements"`
    Relationships map[string]RelationshipKind  `json:"relationships,omitempty"`
    Tags          []TagDefinition              `json:"tags,omitempty"`
    Patterns      map[string]PatternDefinition `json:"patterns,omitempty"`
    Decisions     []DecisionRecord             `json:"decisions,omitempty"`
}

type ElementKind struct {
    Notation    string `json:"notation"`
    Description string `json:"description,omitempty"`
    Container   bool   `json:"container,omitempty"`
}

type RelationshipKind struct {
    Notation string `json:"notation"`
    Dashed   bool   `json:"dashed,omitempty"`
}

// Element represents an architecture building block
type Element struct {
    Kind         string             `json:"kind"`
    Title        string             `json:"title"`
    Description  string             `json:"description,omitempty"`
    Technology   string             `json:"technology,omitempty"`
    Tags         []string           `json:"tags,omitempty"`
    Status       string             `json:"status,omitempty"`       // proposed|design|implementation|deployed|deprecated|archived
    Decisions    []string           `json:"decisions,omitempty"`    // linked ADR IDs
    LastModified string             `json:"lastModified,omitempty"` // RFC3339; overrides git-based staleness
    Children     map[string]Element `json:"children,omitempty"`
    Metadata     map[string]string  `json:"metadata,omitempty"`
}

// Relationship connects two elements
type Relationship struct {
    From        string   `json:"from"`
    To          string   `json:"to"`
    Label       string   `json:"label,omitempty"`
    Kind        string   `json:"kind,omitempty"`
    Description string   `json:"description,omitempty"`
    Decisions   []string `json:"decisions,omitempty"`   // linked ADR IDs
    Cardinality string   `json:"cardinality,omitempty"` // 1:1|1:N|N:N
    DataFlow    string   `json:"dataFlow,omitempty"`    // sync|async|request/response|publish/subscribe
}

// View defines which elements appear in a diagram
type View struct {
    Title       string   `json:"title"`
    Scope       string   `json:"scope,omitempty"`
    Include     []string `json:"include,omitempty"`
    Exclude     []string `json:"exclude,omitempty"`
    FilterTags  []string `json:"filter-tags,omitempty"`  // include only elements with ALL of these tags
    ExcludeTags []string `json:"exclude-tags,omitempty"` // exclude elements with ANY of these tags
    Description string   `json:"description,omitempty"`
    Layout      string   `json:"layout,omitempty"` // layered (default)|grid|none
}

// DynamicView describes a sequence diagram scenario
type DynamicView struct {
    Key         string         `json:"key"`
    Title       string         `json:"title"`
    Description string         `json:"description,omitempty"`
    Steps       []SequenceStep `json:"steps"`
}

// SequenceStep is one message in a dynamic view
type SequenceStep struct {
    Index int      `json:"index"`
    From  string   `json:"from"`
    To    string   `json:"to"`
    Label string   `json:"label"`
    Type  StepType `json:"type,omitempty"`
}

type StepType string

const (
    StepSync   StepType = "sync"
    StepAsync  StepType = "async"
    StepReturn StepType = "return"
)

// Config holds top-level configuration for diagram generation
type Config struct {
    Metadata *bool  `json:"metadata,omitempty"`
    Legend   *bool  `json:"legend,omitempty"`
    Author   string `json:"author,omitempty"`
    Repo     string `json:"repo,omitempty"`
}

// ModelSnapshot is an as-is or to-be capture used by the diff command
type ModelSnapshot struct {
    Elements      map[string]Element `json:"elements"`
    Relationships []Relationship     `json:"relationships"`
}