Skip to content

ArchiMate in Structurizr DSL — And Beyond

Context

The Semantic Architecture as Code article showed that RDF/Turtle can serve as an architecture DSL with ArchiMate's full semantic richness. But Turtle is verbose and unfamiliar to most developers.

This article asks three questions:

  1. Can we model ArchiMate semantics in Structurizr DSL — using its archetypes, tags, properties, and custom elements — and then convert to RDF?
  2. How does that compare to writing Turtle directly?
  3. Is there a better DSL — something with Structurizr's developer-friendliness AND Turtle's semantic richness?

1. Can Structurizr DSL Express ArchiMate? — A Practical Evaluation

Structurizr DSL has three extension mechanisms that could carry ArchiMate semantics:

1.1 Archetypes — user-defined element types

Since 2024, Structurizr supports archetypes: user-defined types that extend the basic C4 element types with default tags, technology, and properties.

archetypes {
    businessActor = person {
        tag "ArchiMate" "BusinessActor"
    }
    applicationComponent = softwareSystem {
        tag "ArchiMate" "ApplicationComponent"
    }
    applicationService = container {
        tag "ArchiMate" "ApplicationService"
    }
    applicationInterface = container {
        tag "ArchiMate" "ApplicationInterface"
    }
    dataObject = container {
        tag "ArchiMate" "DataObject"
    }
    node = deploymentNode {
        tag "ArchiMate" "Node"
    }
}

This lets you write:

model {
    customer = businessActor "Customer"
    orderMgmt = applicationComponent "Order Management" {
        orderService = applicationService "Order Placement Service"
        orderAPI = applicationInterface "Orders REST API"
        ordersDB = dataObject "Order"
    }
}

What works: Element typing. You can tag elements with ArchiMate types and a converter can read those tags to produce typed RDF.

What breaks: - Archetypes are aliases for C4 types. An applicationService archetype is still a container underneath. Structurizr's view rules still apply — you can't put an applicationService in a System Context view because it's a container. ArchiMate's viewpoint rules are completely different. - No cross-layer nesting. In ArchiMate, a Business Service can be realized by an Application Service. In Structurizr, a container can only live inside a software system. You can't nest a "Business Service" archetype inside a "Capability" archetype — the DSL doesn't allow it. - Only 8 base types. Archetypes can only extend: person, softwareSystem, container, component, deploymentNode, infrastructureNode, group, element. ArchiMate has 62 element types across 7 layers. Many ArchiMate concepts (Capability, Value Stream, Goal, Principle, Requirement, Business Process, Technology Service) don't map cleanly to any C4 base type.

1.2 Custom elements — the element keyword

Structurizr's element keyword creates elements outside the C4 model:

model {
    orderFulfillment = element "Order Fulfillment" "Capability" {
        tag "ArchiMate" "Capability"
    }
    orderPlacement = element "Order Placement" "BusinessService" {
        tag "ArchiMate" "BusinessService"
    }
}

What works: You can create any ArchiMate element type as a custom element.

What breaks: - Custom elements can only appear in custom views. They cannot appear in System Context, Container, Component, or Deployment views. This defeats the purpose — you can't show a Capability in the same diagram as an Application Component. - No containment hierarchy. Custom elements are flat. You can't nest a Business Process inside a Business Actor, or an Application Function inside an Application Component. - No typed relationships. All relationships are still labelled arrows. am:serves, am:realizes, am:accesses — these become free-text labels with no formal semantics.

1.3 Properties — key-value metadata

Structurizr elements can carry arbitrary properties:

orderMgmt = softwareSystem "Order Management" {
    properties {
        "archimate:type" "ApplicationComponent"
        "archimate:layer" "Application"
        "owner" "Orders Team"
        "lifecycle" "production"
    }
}

What works: A converter can read these properties and produce typed RDF.

What breaks: Properties are opaque strings. Structurizr doesn't validate them. You can write "archimate:type" "Bananaphone" and Structurizr won't complain. There's no schema, no validation, no auto-completion.

1.4 Relationship archetypes

Structurizr supports relationship archetypes with default tags and technology:

archetypes {
    serves = -> { tags "ArchiMate" "Serving" }
    realizes = -> { tags "ArchiMate" "Realization" }
    accesses = -> { tags "ArchiMate" "Access" }
    composedOf = -> { tags "ArchiMate" "Composition" }
    assignedTo = -> { tags "ArchiMate" "Assignment" }
}

orderService --realizes-> orderPlacement "Realizes"
orderAPI --serves-> customer "Serves"

What works: Relationship typing via tags. A converter can read the tags.

What breaks: The tags are just strings. Structurizr doesn't enforce that a realizes relationship can only go from an Application Service to a Business Service. Any element can realizes any other element. The ArchiMate relationship validity matrix (which source types can connect to which target types via which relationship types) is completely unenforced.

1.5 Verdict: Possible but Leaky

You CAN model ArchiMate in Structurizr DSL using archetypes + tags + properties + relationship archetypes. A converter can read the tags and properties and produce typed RDF. But:

ArchiMate Capability Structurizr DSL Support Gap
62 element types ⚠️ Via archetypes + custom elements + tags Only 8 base types; custom elements can't appear in standard views
7 layers with cross-layer nesting C4 nesting rules (System → Container → Component) don't match ArchiMate layers
11 typed relationship types ⚠️ Via relationship archetypes + tags No validation of source/target type constraints
Relationship validity matrix Structurizr doesn't validate which types can connect via which relationships
Viewpoints (23+ with element/relationship filtering) Structurizr views are C4-scoped, not ArchiMate-scoped
Derivation rules No inference capability
Cross-layer traceability queries No query language

The result is ArchiMate-flavoured C4, not actual ArchiMate. The types are carried as metadata, but the structural rules, viewpoint constraints, and relationship semantics are lost. A converter to RDF would recover the semantics, but the authoring experience in Structurizr DSL doesn't enforce them.


2. The Full Example: ArchiMate in Structurizr DSL

Despite the limitations, here's what the best-effort ArchiMate-in-Structurizr looks like:

workspace "Order Management Architecture" {

    model {
        archetypes {
            # ArchiMate element archetypes
            businessActor = person { tag "am:BusinessActor" }
            capability = element { tag "am:Capability" }
            businessService = element { tag "am:BusinessService" }
            applicationComponent = softwareSystem { tag "am:ApplicationComponent" }
            applicationService = container { tag "am:ApplicationService" }
            applicationInterface = container { tag "am:ApplicationInterface" }
            dataObject = container { tag "am:DataObject" }
            techNode = deploymentNode { tag "am:Node" }
            systemSoftware = deploymentNode { tag "am:SystemSoftware" }

            # ArchiMate relationship archetypes
            serves = -> { tags "am:Serving" }
            realizes = -> { tags "am:Realization" }
            accesses = -> { tags "am:Access" }
            composedOf = -> { tags "am:Composition" }
            assignedTo = -> { tags "am:Assignment" }
        }

        # ── Strategy / Business ──────────────────────────
        orderFulfillment = capability "Order Fulfillment"
        orderPlacementBiz = businessService "Order Placement"

        # ── People ───────────────────────────────────────
        customer = businessActor "Customer"

        # ── Application Layer ────────────────────────────
        orderMgmt = applicationComponent "Order Management" {
            properties {
                "owner" "Orders Team"
            }
            orderPlacementSvc = applicationService "Order Placement Service"
            orderAPI = applicationInterface "Orders REST API"
            ordersAPIComp = container "Orders API" "Handles order lifecycle" "Spring Boot"
            ordersDB = container "Orders DB" "Stores orders" "PostgreSQL"
            ordersWorker = container "Orders Worker" "Processes events" "Spring Boot"
            orderData = dataObject "Order"
        }

        paymentSystem = applicationComponent "Payment System" "External"

        # ── Relationships ────────────────────────────────
        customer --serves-> orderAPI "Uses"
        orderPlacementSvc --realizes-> orderPlacementBiz
        orderPlacementBiz --realizes-> orderFulfillment
        ordersAPIComp --serves-> ordersDB "Reads/writes" "JDBC"
        ordersAPIComp --serves-> ordersWorker "Sends events" "AMQP"
        ordersAPIComp --serves-> paymentSystem "Processes payments" "HTTPS"
        ordersAPIComp --accesses-> orderData "Reads/writes"
        ordersAPIComp --assignedTo-> orderPlacementSvc

        # ── Deployment ───────────────────────────────────
        production = deploymentEnvironment "Production" {
            aws = techNode "AWS eu-west-1" {
                eks = techNode "EKS Cluster" "" "Kubernetes 1.28" {
                    containerInstance ordersAPIComp
                    containerInstance ordersWorker
                }
                rds = techNode "RDS" "" "PostgreSQL 15" {
                    containerInstance ordersDB
                }
            }
        }
    }

    views {
        # Standard C4 views still work
        container orderMgmt "Containers" {
            include *
            autolayout lr
        }

        deployment orderMgmt production "Production" {
            include *
            autolayout lr
        }

        # Custom view for ArchiMate cross-layer
        custom "BusinessAlignment" "Business Alignment" {
            include orderFulfillment
            include orderPlacementBiz
            include orderPlacementSvc
            include orderMgmt
            autolayout tb
        }

        styles {
            element "am:Capability" {
                shape roundedbox
                background #f0e68c
            }
            element "am:BusinessService" {
                shape roundedbox
                background #ffe4b5
            }
            element "am:ApplicationService" {
                background #b0e0e6
            }
            element "am:DataObject" {
                shape cylinder
                background #d3d3d3
            }
        }
    }
}

What this achieves: A developer-readable file in Git that carries ArchiMate type information via tags and can be converted to RDF. The C4 Container and Deployment views still work. A custom view shows the cross-layer alignment.

What this loses: No SHACL validation. No relationship validity checking. No derivation rules. No SPARQL queries. The cross-layer view is manually curated, not automatically generated from viewpoint definitions. The ArchiMate types are metadata, not enforced structure.


3. Comparison: Structurizr DSL vs Turtle vs Alternatives

3.1 The same model in three formats

Structurizr DSL (~40 lines of model, shown above) - Readable by any developer - Tags carry ArchiMate types - No semantic validation - Converter needed to produce RDF

RDF/Turtle (~50 lines, from the previous article) - Full ArchiMate semantics - SHACL validation - SPARQL queryable - Verbose, unfamiliar syntax

YAML-LD (hypothetical — what it could look like):

"@context":
  am: https://meta.linked.archi/archimate3/onto#
  bs: https://meta.linked.archi/backstage/onto#
  arch: https://meta.linked.archi/core#
  skos: http://www.w3.org/2004/02/skos/core#
  ex: https://model.example.com/orders#

"@graph":
  - "@id": ex:OrderFulfillment
    "@type": am:Capability
    skos:prefLabel: "Order Fulfillment"

  - "@id": ex:OrderPlacement
    "@type": am:BusinessService
    skos:prefLabel: "Order Placement"
    am:realizes: { "@id": ex:OrderFulfillment }

  - "@id": ex:OrderManagement
    "@type": am:ApplicationComponent
    skos:prefLabel: "Order Management"
    bs:ownedBy: { "@id": ex:OrdersTeam }
    am:composedOf:
      - { "@id": ex:OrdersAPI }
      - { "@id": ex:OrdersDB }

  - "@id": ex:OrderPlacementService
    "@type": am:ApplicationService
    skos:prefLabel: "Order Placement Service"
    am:realizes: { "@id": ex:OrderPlacement }

  - "@id": ex:OrdersAPI
    "@type": am:ApplicationComponent
    skos:prefLabel: "Orders API"
    skos:definition: "Handles order lifecycle"
    am:serves: { "@id": ex:PaymentSystem }
    am:accesses: { "@id": ex:Order }
    am:assignedTo: { "@id": ex:SpringBoot }

LinkML YAML (hypothetical — schema-driven):

# Defined by a LinkML schema that maps to ArchiMate ontology
elements:
  - id: order-fulfillment
    type: Capability
    label: "Order Fulfillment"

  - id: order-placement
    type: BusinessService
    label: "Order Placement"
    realizes: order-fulfillment

  - id: order-management
    type: ApplicationComponent
    label: "Order Management"
    owner: orders-team
    composed_of: [orders-api, orders-db, orders-worker]

  - id: order-placement-service
    type: ApplicationService
    label: "Order Placement Service"
    realizes: order-placement

  - id: orders-api
    type: ApplicationComponent
    label: "Orders API"
    description: "Handles order lifecycle"
    technology: "Spring Boot"
    serves: [payment-system]
    accesses: [order-data]
    assigned_to: [order-placement-service]

3.2 Comparison matrix

Dimension Structurizr DSL RDF/Turtle YAML-LD LinkML YAML
Readability ✅ Excellent ⚠️ Moderate (prefix-heavy) ⚠️ Moderate (JSON-LD verbosity) ✅ Excellent
Learning curve ✅ Hours ⚠️ Days ⚠️ Days (JSON-LD context) ✅ Hours (just YAML)
ArchiMate type system ⚠️ Via tags (unenforced) ✅ Native OWL classes ✅ Native (via @type) ✅ Via schema (validated)
Typed relationships ⚠️ Via tags (unenforced) ✅ Native OWL properties ✅ Native (via predicates) ✅ Via schema slots
Relationship validation ✅ SHACL ✅ SHACL (after conversion) ✅ LinkML validation
Cross-layer traceability ❌ (manual custom views) ✅ SPARQL ✅ SPARQL (after conversion) ✅ (after RDF generation)
SPARQL queryable ✅ Native ✅ (is RDF) ✅ (generates RDF)
AI agent access (MCP) ✅ (via generated RDF)
Diagram generation ✅ Native (polished) ⚠️ Via generators ⚠️ Via generators ⚠️ Via generators
Git-friendly
IDE support ✅ (VS Code extension) ⚠️ (generic Turtle highlighting) ⚠️ (YAML highlighting) ✅ (YAML + schema validation)
Ecosystem maturity ✅ Mature ✅ Mature (W3C) ⚠️ Emerging (W3C CG) ⚠️ Growing (CNCF-adjacent)
Converter to RDF needed Yes (custom) No (is RDF) No (is RDF) Yes (built-in gen-rdf)

4. The Options — Ranked

Option A: Structurizr DSL + Converter (pragmatic, limited)

How it works today: Teams write Structurizr DSL, export it to workspace JSON, and run structurizr2linkedarchi to produce RDF typed with C4 and Structurizr classes. A separate, custom projection could interpret ArchiMate archetypes and tags as ArchiMate classes, but the current converter deliberately does not make that cross-language inference.

Pros: - Developers already know Structurizr DSL - Polished diagram generation - Existing ecosystem (VS Code extension, Lite, Cloud) - C4/Structurizr RDF conversion is available now

Cons: - ArchiMate semantics carried only as tags remain metadata and are not projected by the current converter - Cross-layer modelling is awkward (custom elements in custom views) - A tag-driven ArchiMate projection must be built and maintained if required - The DSL, exported JSON, and generated RDF must be kept in one reproducible pipeline

Best for: Teams already using Structurizr that want their C4 model in a Linked.Archi graph. Teams that also require ArchiMate typing should treat the tag projection as an explicit extension, not current converter behavior.

Option B: RDF/Turtle directly (powerful, verbose)

How it works: Teams write Turtle files directly, as shown in the Semantic Architecture as Code article.

Pros: - Full ArchiMate semantics, no compromises - SHACL validation at authoring time - SPARQL queryable immediately - No converter needed - MCP server access immediately

Cons: - Turtle syntax is unfamiliar to most developers - Verbose (prefix declarations, IRI syntax) - No native diagram generation (need generators) - IDE support is basic (syntax highlighting, no schema-aware auto-completion)

Best for: Architecture teams comfortable with semantic web technologies, or organizations where the EA team authors models and developers consume generated outputs.

Option C: YAML-LD (semantic, familiar syntax)

How it works: Teams write YAML files with a JSON-LD @context that maps keys to ArchiMate ontology IRIs. The YAML is valid RDF (via the YAML-LD specification) and can be loaded directly into a triplestore.

Pros: - YAML is familiar to every developer (Kubernetes, CI/CD, Backstage) - Full RDF semantics (it IS RDF, just in YAML syntax) - No converter needed — YAML-LD parsers produce RDF directly - SHACL validation works on the parsed RDF

Cons: - YAML-LD is a W3C Community Group specification, not yet a full standard - The @context / @graph / @id / @type syntax adds JSON-LD ceremony - Tooling is immature compared to Turtle or JSON-LD - No native diagram generation

Best for: Organizations that want semantic precision in a developer-familiar format and are willing to adopt an emerging standard.

Option D: LinkML YAML + ArchiMate Schema (best of both worlds?)

How it works: Define an ArchiMate schema in LinkML (a YAML-based data modelling language). Teams write architecture models as plain YAML conforming to the schema. LinkML generates RDF, JSON Schema, SHACL shapes, and documentation from the schema.

Pros: - Authoring is plain YAML — no @context, no prefixes, no IRIs - Schema provides validation and auto-completion at authoring time - LinkML generates RDF/OWL, SHACL, JSON Schema, Python dataclasses, documentation - The schema IS the metamodel — ArchiMate element types, relationship constraints, and cardinalities are defined once - Active community (biomedical data, CNCF-adjacent)

Cons: - Requires defining an ArchiMate schema in LinkML (significant upfront work) - LinkML's relationship modelling is less expressive than OWL for qualified relationships - Generated RDF may not perfectly match Linked.Archi's qualified relationship pattern without customization - Another tool in the chain

Best for: Organizations that want the simplest possible authoring experience with full semantic validation, and are willing to invest in schema definition upfront.

Option E: A Linked.Archi DSL (hypothetical — purpose-built)

How it works: A purpose-built DSL designed specifically for semantic architecture modelling, combining Structurizr's terseness with ArchiMate's type system.

What it could look like:

@metamodel archimate 3.2
@namespace ex https://model.example.com/orders#

# ── Strategy ─────────────────────────────────────
capability "Order Fulfillment"

# ── Business ─────────────────────────────────────
business-service "Order Placement"
    realizes "Order Fulfillment"

business-actor "Customer"

# ── Application ──────────────────────────────────
application-component "Order Management"
    owner "Orders Team"
    composed-of "Orders API", "Orders DB", "Orders Worker"

application-service "Order Placement Service"
    realizes "Order Placement"

application-component "Orders API"
    description "Handles order lifecycle"
    technology "Spring Boot"
    serves "Payment System"
    accesses "Order"
    assigned-to "Order Placement Service"

application-component "Orders DB"
    description "Stores orders"
    technology "PostgreSQL"

data-object "Order"

# ── Technology ───────────────────────────────────
node "EKS Cluster"
    system-software "Kubernetes 1.28"
    deploys "Orders API", "Orders Worker"

node "RDS Instance"
    system-software "PostgreSQL 15"
    deploys "Orders DB"

Pros: - Extremely readable — no prefixes, no IRIs, no @context - ArchiMate types are keywords, not tags - Relationships are typed by the DSL grammar - A parser produces RDF aligned to the Linked.Archi ontology - SHACL validation runs on the output - Could support Structurizr-style view definitions

Cons: - Doesn't exist yet — requires designing and implementing a parser - Another DSL to learn (though simpler than Turtle or Structurizr) - Maintenance burden of a custom language - Risk of reinventing what LinkML already provides

Best for: The Linked.Archi ecosystem, if the community decides the authoring experience is the critical adoption barrier.


5. Recommendation

There is no single best option. The choice depends on where the organization is:

Starting point Recommended path
Already using Structurizr and want C4 semantics in RDF Option A — keep the DSL workflow, export workspace JSON, and run the existing Structurizr converter
Already using Structurizr and require ArchiMate typing Extend Option A with an explicit, reviewable tag-to-ArchiMate mapping; this is not built into the current converter
EA team comfortable with semantic web, developers consume outputs Option B — Turtle directly. Maximum power, no compromises. Developers can consume generated Markdown and HTML
Want semantic precision in a developer-familiar format Option C or D — YAML-LD or LinkML. YAML authoring with RDF output. LinkML adds schema validation
Building the Linked.Archi ecosystem and want maximum adoption Option E — purpose-built DSL. Highest investment, highest payoff for developer experience
Want to start today with minimal investment Option A when using Structurizr; otherwise Option B

Linked.Archi supports Turtle natively and ships structurizr2linkedarchi for Structurizr workspace JSON. The converter preserves C4 and Structurizr semantics; it is not a PlantUML extension and does not reinterpret ArchiMate tags. Options C, D, E, and the tag-driven ArchiMate projection remain possible future directions.


6. Existing Converter and Possible ArchiMate Extension

The supported pipeline is:

flowchart TD
    DSL["Structurizr DSL"]
    JSON["Structurizr Workspace JSON"]
    RDF["RDF dataset<br/>(C4 + Structurizr semantics)"]
    SHACL["SHACL validation"]
    SPARQL["SPARQL queries"]
    MCP["MCP server (AI agents)"]
    Docs["rdf2docs (Markdown generation)"]
    Nav["Static navigator (web UI)"]

    DSL -->|"Structurizr CLI: export to JSON"| JSON
    JSON -->|"structurizr2linkedarchi"| RDF
    RDF --> SHACL & SPARQL & MCP & Docs & Nav

The current converter:

  1. Reads Structurizr workspace JSON, not DSL directly.
  2. Maps C4 people, software systems, containers, components, relationships, deployment structures, views, ADRs, and supported metadata to the published C4, Structurizr, and decision ontologies.
  3. Produces semantic, model, views, and provenance graph roles, and asserts arch:partOfModel on model concepts. Structurizr supplies view membership but no authored coordinates, so the views graph has no geometry and no-geometry makes no difference.
  4. Emits qualified relationship resources and SKOS labels/notations according to its CLI options.
  5. Supports explicit YAML type mapping for source kinds; it does not infer a second modelling language from tags.

A future tag-driven ArchiMate projection could additionally map am:ApplicationComponent tags to ArchiMate classes, map relationship archetypes to ArchiMate relationship types, and map organization-owned properties such as ownership or lifecycle state. Those mappings need an explicit contract because they mix C4, ArchiMate, Backstage, and local vocabularies; presenting them as current behavior would make the graph claim semantics that the implemented converter does not infer.


7. Summary

Question Answer
Can Structurizr DSL express ArchiMate? Partially, through archetypes and tags, but without ArchiMate structural enforcement, relationship validity, or viewpoint rules
Can Linked.Archi convert Structurizr today? Yes. Export workspace JSON and run structurizr2linkedarchi; the output uses C4 and Structurizr semantics
Does the current converter turn ArchiMate tags into ArchiMate RDF? No. That is a possible custom extension and should use an explicit mapping contract
Is Turtle better? Semantically, Turtle can express full ArchiMate directly. Practically, the choice depends on who authors and who consumes the model
Is there a better DSL? YAML-LD and LinkML are promising alternatives. A purpose-built Linked.Archi DSL could improve authoring but requires investment
What should I do today? For Structurizr, export JSON and use the existing converter. Add a custom ArchiMate projection only when that cross-language mapping is actually required

References