ADR-001: Choice of DSL Format for Bausteinsicht

Status

Accepted

Context

Bausteinsicht is an architecture-as-code tool that uses draw.io as its visual frontend. A central element is the DSL (Domain-Specific Language) in which the architecture model is defined. The DSL must support bidirectional synchronization with draw.io.

The choice of DSL format significantly influences:

  • How quickly new users become productive

  • What IDE support we get for free vs. need to build ourselves

  • How well LLMs (AI agents) can read and write the DSL

  • How much development effort goes into parser and tooling

Constraints

  • Target audience: developers and software architects

  • Bidirectional sync with draw.io requires stable IDs and machine-readable structure

  • LLM-assisted usage is a key goal (CLI commands for AI agents)

Evaluated Options

Option A: JSON with JSON Schema (JSONC)

The architecture is described in JSON files with comments (JSONC). A JSON Schema defines the structure and enables automatic validation and IDE support.

Example
{
  // Bausteinsicht architecture model
  "specification": {
    "elements": {
      "actor": { "notation": "Actor", "description": "A person or external system" },
      "system": { "notation": "Software System", "description": "A top-level system", "container": true },
      "container": { "notation": "Container", "description": "A deployable unit", "container": true },
      "component": { "notation": "Component", "description": "A logical grouping", "container": true }
    }
  },
  "model": {
    "customer": {
      "kind": "actor",
      "title": "Customer",
      "description": "End user of the webshop"
    },
    "webshop": {
      "kind": "system",
      "title": "Webshop",
      "children": {
        "api": {
          "kind": "container",
          "title": "REST API",
          "technology": "Spring Boot"
        },
        "db": {
          "kind": "container",
          "title": "Database",
          "technology": "PostgreSQL"
        }
      }
    }
  },
  "relationships": [
    { "from": "customer", "to": "webshop.api", "label": "uses" },
    { "from": "webshop.api", "to": "webshop.db", "label": "reads/writes" }
  ]
}
  • No custom parser needed — every language has JSON support

  • IDE support via JSON Schema for free in all editors (VS Code, IntelliJ, Neovim, …​)

  • Publication on SchemaStore.org enables automatic schema recognition

  • LLMs generate JSON natively and reliably

  • JSONC allows comments for better readability

  • Downside: more verbose than a custom DSL

Option B: TypeScript DSL (npm Package)

The architecture is defined as TypeScript code that imports a Bausteinsicht library.

Example
import { model, actor, system, container, rel } from "bausteinsicht";

const customer = actor("customer", {
  title: "Customer",
  description: "End user of the webshop"
});

const webshop = system("webshop", {
  title: "Webshop",
  children: {
    api: container("api", {
      title: "REST API",
      technology: "Spring Boot"
    }),
    db: container("db", {
      title: "Database",
      technology: "PostgreSQL"
    })
  }
});

rel(customer, webshop.api, "uses");
rel(webshop.api, webshop.db, "reads/writes");
  • No custom parser needed — publish an npm package

  • Excellent IDE support everywhere TypeScript is supported

  • LLMs generate TypeScript very reliably (large training corpus)

  • Type safety provides autocompletion and error checking

  • Downside: looks like code, not configuration

  • Downside: bidirectional sync harder (code generation on back-sync from draw.io)

  • Downside: Node.js runtime required

Option C: Custom DSL (via Langium) — Reference

A purpose-built language optimized for architecture modeling. Parser and LSP server are generated using Langium.

Example
specification {
  element actor
  element system
  element container
  element component
}

model {
  customer = actor "Customer" {
    description "End user of the webshop"
  }

  webshop = system "Webshop" {
    api = container "REST API" {
      technology "Spring Boot"
    }
    db = container "Database" {
      technology "PostgreSQL"
    }
    api -> db "reads/writes"
  }

  customer -> webshop.api "uses"
}
  • Maximum control over syntax and readability

  • Potentially the most compact and elegant representation

  • Langium generates LSP server and VS Code extension

  • Downside: weeks of development effort for parser, LSP, VS Code extension

  • Downside: LLMs do not know the syntax (no training corpus)

  • Downside: IDE support only in editors with LSP support

  • Downside: Langium ties to TypeScript/Node.js

Option D: Structurizr DSL

The established DSL from the Structurizr ecosystem (Simon Brown / C4 model). Used by Structurizr, Structurizr Lite, and various community tools.

Example
workspace {
  model {
    customer = person "Customer" "End user of the webshop"

    webshop = softwareSystem "Webshop" {
      api = container "REST API" "Handles HTTP requests" "Spring Boot"
      db = container "Database" "Stores data" "PostgreSQL"
    }

    customer -> webshop.api "uses"
    webshop.api -> webshop.db "reads/writes"
  }

  views {
    systemContext webshop "Context" {
      include *
      autoLayout
    }
  }
}
  • Well-established in the C4 community with large user base

  • Rich ecosystem: Structurizr, Structurizr Lite, CLI, various exporters

  • Views, deployment, and styling built into the DSL

  • Downside: custom parser required (no standard grammar format)

  • Downside: tightly coupled to the C4 model’s fixed 4 levels (person, softwareSystem, container, component)

  • Downside: bidirectional sync not designed for — DSL is source-of-truth only

  • Downside: LLMs know the syntax but make frequent mistakes with its specific keywords

Option E: LikeC4 DSL

A newer C4-inspired DSL with a modern developer experience. Uses Langium for parsing, ships with a VS Code extension and a web-based viewer.

Example
specification {
  element actor
  element system
  element container
  element component
}

model {
  customer = actor 'Customer' {
    description 'End user of the webshop'
  }

  webshop = system 'Webshop' {
    api = container 'REST API' {
      technology 'Spring Boot'
    }
    db = container 'Database' {
      technology 'PostgreSQL'
    }

    api -> db 'reads/writes'
  }

  customer -> webshop.api 'uses'
}

views {
  view context of webshop {
    include *
  }
}
  • Flexible element hierarchy (not limited to C4’s 4 levels) — similar to Bausteinsicht

  • Modern tooling: live preview, VS Code extension, web viewer

  • Specification-based element kinds (user-defined, not hardcoded)

  • Downside: custom parser required (Langium-based, TypeScript/Node.js)

  • Downside: relatively new project, smaller community than Structurizr

  • Downside: LLMs have very limited training data for this syntax

  • Downside: bidirectional sync not a design goal — DSL is source-of-truth only

Option F: PlantUML C4

Architecture modeling using PlantUML with the C4-PlantUML extension library. Leverages the widely-used PlantUML rendering engine.

Example
@startuml
!include <C4/C4_Context>

Person(customer, "Customer", "End user of the webshop")

System_Boundary(webshop, "Webshop") {
  Container(api, "REST API", "Spring Boot", "Handles HTTP requests")
  Container(db, "Database", "PostgreSQL", "Stores data")
}

Rel(customer, api, "uses")
Rel(api, db, "reads/writes")

@enduml
  • Massive ecosystem: PlantUML is one of the most widely used diagramming tools

  • LLMs generate PlantUML very reliably (huge training corpus)

  • Renders directly to PNG/SVG without external tools

  • Supported by many documentation frameworks (AsciiDoc, Markdown, Confluence)

  • Downside: macro-based syntax is not a true data model — difficult to parse reliably

  • Downside: bidirectional sync essentially impossible (rendering language, not a data model)

  • Downside: no JSON Schema or structured validation possible

  • Downside: element IDs are positional macro arguments, not stable keys

  • Downside: fixed to C4 macros (Person, Container, Component) — no custom element kinds

Weighted Pugh Matrix

Rating scale: -1 = worse than reference, 0 = same as reference, +1 = better than reference

Reference option: C (Custom DSL)

Criterion Weight A: JSON+Schema B: TypeScript C: Custom DSL (Ref) D: Structurizr E: LikeC4 F: PlantUML C4

Learnability

5

+1

-1

0

0

0

+1

IDE support (breadth & effort)

5

+1

+1

0

-1

0

+1

LLM friendliness

4

+1

+1

0

0

-1

+1

Parser / tooling effort

4

+1

+1

0

0

0

-1

Bidirectional sync suitability

5

+1

-1

0

-1

-1

-1

Syntax compactness

3

-1

-1

0

0

0

-1

Portability (no runtime)

2

+1

-1

0

+1

-1

+1

Expressiveness

2

-1

+1

0

-1

+1

-1

Weighted Results

Criterion A: JSON+Schema B: TypeScript C: Custom DSL (Ref) D: Structurizr E: LikeC4 F: PlantUML C4

Learnability (×5)

+5

-5

0

0

0

+5

IDE support (×5)

+5

+5

0

-5

0

+5

LLM friendliness (×4)

+4

+4

0

0

-4

+4

Parser / tooling effort (×4)

+4

+4

0

0

0

-4

Bidirectional sync (×5)

+5

-5

0

-5

-5

-5

Syntax compactness (×3)

-3

-3

0

0

0

-3

Portability (×2)

+2

-2

0

+2

-2

+2

Expressiveness (×2)

-2

+2

0

-2

+2

-2

Total

+20

0

0

-10

-9

+2

Decision

Option A: JSON with JSON Schema (JSONC)

JSON with JSON Schema offers the best overall package for Bausteinsicht:

  1. Immediate start: No parser development effort — we can focus on the core feature (draw.io synchronization).

  2. Bidirectional sync: JSON is trivial to read, modify, and write back. With TypeScript code, back-generation from draw.io is significantly more complex.

  3. Universal IDE support: JSON Schema provides autocompletion, validation, and hover documentation in all major editors with zero extra effort.

  4. LLM-native: LLMs generate JSON more reliably than any other format. This enables AI-assisted architecture maintenance via CLI commands.

  5. Comments via JSONC: The main argument against JSON (no comments) is mitigated by JSONC.

The lower compactness compared to a custom DSL is an acceptable trade-off given the massive advantages in tooling, sync, and LLM integration.

Consequences

Positive

  • Fast project start without parser development

  • IDE support in all editors from day 1

  • LLMs can reliably read and write architecture files

  • JSON Schema serves as living documentation of the model format

  • Easy integration into CI/CD pipelines (JSON validation)

Negative

  • Architecture files are more verbose than a custom DSL

  • JSON syntax requires braces and quotes (more typing)

  • No native variables or references within the DSL

  • Relationships must be expressed via string IDs ("from": "webshop.api") rather than direct references

Risks

  • If verbosity becomes too disruptive in practice, a converter from a more compact syntax to JSON can be built later (the JSON foundation remains)

  • JSONC is not supported by all JSON parsers — we need to choose a JSONC-capable parser