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 labeled 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-flavored 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: Teams write Structurizr DSL with ArchiMate archetypes and tags. A custom converter reads the Structurizr workspace JSON and produces RDF typed with ArchiMate classes.

Pros: - Developers already know Structurizr DSL - Polished diagram generation - Existing ecosystem (VS Code extension, Lite, Cloud)

Cons: - ArchiMate semantics are metadata, not structure — no validation at authoring time - Cross-layer modeling is awkward (custom elements in custom views) - Converter must be built and maintained - Two representations to keep in sync (DSL + generated RDF)

Best for: Teams already using Structurizr that want to gradually add ArchiMate semantics without changing their workflow.

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 modeling 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 modeling 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 modeling, 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, want to add semantics gradually Option A — Structurizr DSL + converter. Keep the developer workflow, add ArchiMate tags, build a converter.
EA team comfortable with semantic web, developers consume outputs Option B — Turtle directly. Maximum power, no compromises. Developers never see Turtle — they see 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 (if using Structurizr) or Option B (if not). Both work now with existing tools.

The Linked.Archi ecosystem already supports Option B (Turtle) natively and Option A (via the PlantUML converter, which could be extended for Structurizr JSON). Options C, D, and E represent future directions that could dramatically lower the authoring barrier while preserving semantic richness.


6. What a Structurizr-to-RDF Converter Would Do

For teams choosing Option A, here's what the converter pipeline looks like:

flowchart TD
    DSL["Structurizr DSL"]
    JSON["Structurizr Workspace JSON"]
    RDF["RDF/Turtle<br/>(aligned to Linked.Archi ontology)"]
    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 -->|"structurizr-to-rdf converter"| RDF
    RDF --> SHACL & SPARQL & MCP & Docs & Nav

The converter would:

  1. Read the Structurizr workspace JSON (exported via structurizr-cli export -f json)
  2. Map C4 element types to ArchiMate classes based on tags:
  3. Tag am:ApplicationComponenta am:ApplicationComponent
  4. Tag am:BusinessServicea am:BusinessService
  5. No ArchiMate tag → fall back to C4 ontology types (c4:Container, c4:SoftwareSystem)
  6. Map relationship archetypes to ArchiMate predicates based on tags:
  7. Tag am:Servingam:serves + am:Serving qualified relationship
  8. Tag am:Realizationam:realizes + am:Realization qualified relationship
  9. No ArchiMate tag → c4:uses with the label as description
  10. Map properties to RDF properties:
  11. "owner"bs:ownedBy
  12. "lifecycle"bs:lifecycle
  13. "technology"c4:technology
  14. Produce three named graphs: semantic, views, provenance (following the Linked.Archi converter convention)
  15. Emit SKOS labels from element names and descriptions

This converter fits naturally alongside the existing ArchiMate, BPMN, and PlantUML converters in the Linked.Archi toolbox.


7. Summary

Question Answer
Can Structurizr DSL express ArchiMate? Partially. Element types via archetypes/tags, relationship types via relationship archetypes. But no structural enforcement, no cross-layer nesting, no relationship validity, no viewpoint rules.
Is it worth doing? Yes, for teams already using Structurizr. The tags carry enough information for a converter to produce semantically rich RDF. The authoring experience is familiar.
Is Turtle better? Semantically, yes — full ArchiMate with SHACL validation. Practically, it depends on who's authoring. EA teams: Turtle works. Developers: too verbose.
Is there a better DSL? YAML-LD and LinkML are promising alternatives that combine YAML familiarity with RDF semantics. A purpose-built Linked.Archi DSL could offer the best developer experience but requires investment to build.
What should I do today? If using Structurizr: add ArchiMate archetypes and tags, plan a converter. If not: write Turtle directly and generate developer-friendly outputs. Either way, the knowledge graph is the goal.

References