Build Your Own Modeling Tool with Linked.Archi¶
The Build Your Own Metamodel guide ends with a section called "How a Modeling Tool Consumes This" — seven bullet points and a bootstrap query. This article picks up where that one stops. It shows how to build a working modeling tool that loads a Linked.Archi metamodel and provides a palette, canvas, validation, viewpoint filtering, document generation, and AI agent access.
The Cloud Platform Walkthrough provides the worked metamodel example. Everything here uses that metamodel as the running scenario.
What You Are Building¶
A Linked.Archi-aware modeling tool has seven components:
You do not need all seven on day one. The minimum viable tool is components 1–3. Add the rest incrementally.
Technology Choices¶
Two proven stacks, depending on your team:
| Component | TypeScript / Browser | Python / Server |
|---|---|---|
| RDF store | Oxigraph WASM or Comunica | rdflib + Oxigraph (via oxrdflib) |
| SPARQL engine | Oxigraph WASM or Comunica | rdflib or Oxigraph server |
| SHACL validator | rdf-validate-shacl | pyshacl |
| Templating | Handlebars.js | pybars3 or Jinja2 |
| UI framework | React / Svelte / vanilla | Streamlit / Flask / none (CLI) |
| Graph visualization | D3.js / Cytoscape.js / ReactFlow | NetworkX + matplotlib / Graphviz |
| MCP server | @modelcontextprotocol/sdk (TypeScript) | mcp (Python) |
The Linked.Archi ecosystem uses both: the MCP server is TypeScript + Oxigraph, the static navigator is React + Comunica, and the validation pipeline is Java + RDF4J. Pick what fits your team.
This guide shows both stacks side by side where the patterns differ.
Component 1: Metamodel Loader¶
The metamodel manifest (arch:Metamodel) is the single entry point. Load it, follow the properties, and you have the complete modeling language.
The Bootstrap Query¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?property ?value WHERE {
?metamodel a arch:Metamodel ;
skos:prefLabel ?label .
?metamodel ?property ?value .
VALUES ?property {
arch:modelConcepts
arch:formalRules
arch:architectureViewpoints
arch:conceptClassification
arch:hasDeliverableTemplate
arch:referenceData
arch:presentationContextScheme
arch:notationSet
arch:derivationRules
arch:referenceModels
arch:architectureViewpointsRestrictions
}
}
For the Cloud Platform example, this returns:
| property | value |
|---|---|
arch:modelConcepts |
<https://meta.linked.archi/examples/cloudplatform/onto#> |
arch:formalRules |
<https://meta.linked.archi/examples/cloudplatform/shapes#> |
arch:architectureViewpoints |
<https://meta.linked.archi/examples/cloudplatform/viewpoints#> |
arch:conceptClassification |
<https://meta.linked.archi/examples/cloudplatform/tax#> |
arch:hasDeliverableTemplate |
cpdt:ServiceCatalog, cpdt:DeploymentGuide |
arch:referenceData |
cprd:CloudPlatformReferenceData |
TypeScript Implementation¶
import { Store, Parser } from 'n3';
import { QueryEngine } from '@comunica/query-sparql';
async function loadMetamodel(files: string[]): Promise<Store> {
const store = new Store();
const parser = new Parser();
for (const file of files) {
const ttl = await fs.readFile(file, 'utf-8');
const quads = parser.parse(ttl);
store.addQuads(quads);
}
return store;
}
async function bootstrap(store: Store) {
const engine = new QueryEngine();
const result = await engine.queryBindings(`
PREFIX arch: <https://meta.linked.archi/core#>
SELECT ?metamodel ?label ?property ?value WHERE {
?metamodel a arch:Metamodel ; skos:prefLabel ?label .
?metamodel ?property ?value .
VALUES ?property {
arch:modelConcepts arch:formalRules
arch:architectureViewpoints arch:conceptClassification
arch:hasDeliverableTemplate arch:referenceData
arch:presentationContextScheme arch:notationSet
}
}
`, { sources: [store] });
const bindings = await result.toArray();
// Group by property → build a MetamodelDescriptor
return groupByProperty(bindings);
}
Python Implementation¶
from rdflib import Graph, Namespace
ARCH = Namespace("https://meta.linked.archi/core#")
def load_metamodel(files: list[str]) -> Graph:
g = Graph()
for f in files:
g.parse(f, format="turtle")
return g
def bootstrap(g: Graph) -> dict:
query = """
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?metamodel ?label ?property ?value WHERE {
?metamodel a arch:Metamodel ; skos:prefLabel ?label .
?metamodel ?property ?value .
VALUES ?property {
arch:modelConcepts arch:formalRules
arch:architectureViewpoints arch:conceptClassification
arch:hasDeliverableTemplate arch:referenceData
arch:presentationContextScheme arch:notationSet
}
}
"""
results = g.query(query)
return group_by_property(results)
What to Load¶
Load files in dependency order. The metamodel manifest imports the core ontology, which imports nothing. The ontology imports ArchiMate 4.0 (if extending it). Load them all into one graph:
core/core-onto.ttl ← always
modelingLanguages/archimate/4.0/archimate4-onto.ttl ← if extending ArchiMate
examples/custom-metamodel/cloudplatform-onto.ttl ← your metamodel
examples/custom-metamodel/cloudplatform-metamodel.ttl ← the manifest
examples/custom-metamodel/cloudplatform-tax.ttl ← taxonomy
examples/custom-metamodel/cloudplatform-shapes.ttl ← SHACL shapes
examples/custom-metamodel/cloudplatform-viewpoints.ttl
examples/custom-metamodel/cloudplatform-deliverable-templates.ttl
examples/custom-metamodel/cloudplatform-reference-data.ttl
Then load the user's model data on top:
Component 2: Palette Builder¶
The palette is the list of element types and relationship types the user can place on the canvas. It comes entirely from the ontology — no hardcoding.
The Process: What to Use and Why¶
Building a palette requires three semantic assets, each serving a distinct role:
| Semantic Asset | File Pattern | What It Gives the Palette |
|---|---|---|
Ontology (*-onto.ttl) |
OWL classes + properties | The actual element and relationship types — what the user can instantiate |
Taxonomy (*-tax.ttl) |
SKOS concept scheme | The grouping and ordering — how elements are organized in the palette tree |
Notation (*-notation.ttl) |
VisualNotation + Style | The visual appearance — shape, color, icon for each palette entry |
The ontology tells you WHAT exists. The taxonomy tells you HOW TO GROUP IT. The notation tells you HOW TO DRAW IT.
Step-by-step palette generation¶
-
Load the taxonomy — parse the SKOS ConceptScheme. The top-level
skos:Conceptentries become palette groups (e.g., "Business Layer", "Application Layer", "Runtime Services"). Theskos:narrowerlinks point to either sub-groups or leaf nodes (OWL class IRIs). -
Resolve leaf nodes to OWL classes — each leaf in the taxonomy's
skos:narrowertree references an OWL class IRI from the ontology. This is the element type the user will instantiate on drag-drop. Exclude any class annotated witharch:isAbstractClass true— these are structural superclasses for validation/inference, not palette items. -
Enrich with ontology metadata — for each OWL class, read
skos:prefLabel(display name),skos:definition(tooltip), andrdfs:subClassOf(for inheritance-based filtering and connection validation). -
Attach visual notation — query the active NotationSet for each element's
VisualNotation. This gives you the shape, fill color, icon, and rendering mode to draw the palette entry. -
Discover relationship types — query all
rdfs:subClassOf arch:QualifiedRelationshipclasses. Read theirarch:domainIncludes/arch:rangeIncludesto know which connections are valid between which element types. -
Apply viewpoint filter (optional) — if a viewpoint is active, restrict the palette to only the types listed in
arch:includesConcept.
The result is a fully data-driven palette where adding a new element type to the ontology + taxonomy automatically makes it appear in the tool — no code changes needed.
Discovering Element Types¶
The palette should only show concrete (instantiable) element types. Abstract classes — used for classification, validation, and inheritance — are marked with arch:isAbstractClass true and must be excluded:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?label ?definition ?icon ?parent WHERE {
?class rdfs:subClassOf+ arch:Element ;
skos:prefLabel ?label .
FILTER NOT EXISTS { ?class arch:isAbstractClass true }
OPTIONAL { ?class skos:definition ?definition }
OPTIONAL { ?class arch:prefVisNotation ?icon }
OPTIONAL { ?class rdfs:subClassOf ?parent .
?parent rdfs:subClassOf+ arch:Element }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
ORDER BY ?parent ?label
The FILTER NOT EXISTS { ?class arch:isAbstractClass true } line excludes structural superclasses like BehaviorElement, ActiveStructureElement, CommonDomainElement, etc. These exist for inheritance and SHACL validation but are never placed on a canvas by a user.
For the Cloud Platform metamodel, this returns Microservice, APIGateway, Container, KubernetesCluster, MessageBroker, EventTopic, ManagedDatabase, CacheStore, ObservabilityStack — plus all inherited ArchiMate 4.0 concrete types.
Discovering Relationship Types¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?label ?predicate ?sourceDomain ?targetRange WHERE {
?class rdfs:subClassOf arch:QualifiedRelationship ;
skos:prefLabel ?label ;
arch:unqualifiedForm ?predicate .
OPTIONAL { ?predicate arch:domainIncludes ?sourceDomain }
OPTIONAL { ?predicate arch:rangeIncludes ?targetRange }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
This tells the tool: "A Dependency can go from a Microservice to an ApplicationComponent." The tool uses this to enable/disable connection handles on the canvas.
Grouping by Taxonomy¶
The SKOS taxonomy provides palette groups. Elements link to taxonomy concepts via rdfs:seeAlso:
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?group ?groupLabel ?class ?classLabel WHERE {
?class rdfs:seeAlso ?group .
?group a skos:Concept ;
skos:prefLabel ?groupLabel .
?class skos:prefLabel ?classLabel .
FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
FILTER(lang(?classLabel) = "en" || lang(?classLabel) = "")
}
ORDER BY ?groupLabel ?classLabel
Result for the Cloud Platform metamodel:
| Group | Elements |
|---|---|
| Runtime Services | Microservice, APIGateway, ManagedDatabase, CacheStore |
| Integration | MessageBroker, EventTopic |
| Infrastructure | Container, KubernetesCluster |
| Observability | ObservabilityStack |
Building the Palette Data Structure¶
interface PaletteGroup {
iri: string;
label: string;
elements: PaletteItem[];
}
interface PaletteItem {
iri: string; // e.g., cp:Microservice
label: string; // "Microservice"
definition: string; // tooltip text
icon: string; // SVG URL from arch:prefVisNotation
parentClass: string; // e.g., am4:ApplicationComponent
// Visual notation (from NotationSet)
renderingMode?: string; // "ShapeWithBadge" | "IconCentric" | "ShapeOnly"
shapeType?: string; // e.g., "RoundedRectangle"
fillColor?: string; // e.g., "#C9E7CB"
lineColor?: string; // e.g., "#000000"
iconSymbol?: string; // glyph SVG URL (small badge or full icon depending on mode)
iconPlacement?: string; // e.g., "TopRight" or "Center"
width?: number; // default width
height?: number; // default height
}
interface RelationshipType {
iri: string; // e.g., cp:Dependency
label: string; // "Dependency"
predicate: string; // cp:dependsOn
validSources: string[]; // [cp:Microservice]
validTargets: string[]; // [am4:ApplicationComponent]
// Visual notation (from NotationSet)
lineStyle?: string; // e.g., "DashedLine"
sourceDecor?: string; // e.g., "NoDecoration"
targetDecor?: string; // e.g., "OpenArrowhead"
}
Loading Visual Notation from NotationSet¶
Instead of just reading arch:prefVisNotation (a single SVG URL), a full tool reads the NotationSet for structured rendering data:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX arch-vis: <https://meta.linked.archi/core-vis#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
# Get visual notation for all elements in the active notation set
SELECT ?concept ?label ?shape ?fill ?lineColor ?icon ?iconPos ?width ?height ?image ?renderMode
WHERE {
?vn a arch-vis:VisualNotation ;
arch-vis:inNotationSet ?notationSet ;
arch-vis:notationFor ?concept ;
arch-vis:defaultStyle ?style .
?concept skos:prefLabel ?label .
FILTER(lang(?label) = "en")
?style arch-vis:shapeType ?shape ;
arch-vis:fillColor ?fill .
OPTIONAL { ?style arch-vis:lineColor ?lineColor }
OPTIONAL { ?style arch-vis:renderingMode ?renderMode }
OPTIONAL { ?style arch-vis:iconSymbol ?icon }
OPTIONAL { ?style arch-vis:iconPlacement ?iconPos }
OPTIONAL { ?style arch-vis:defaultWidth ?width }
OPTIONAL { ?style arch-vis:defaultHeight ?height }
OPTIONAL { ?vn arch:prefVisNotation ?image }
}
ORDER BY ?label
Context-aware notation selection: To get the notation set for a specific audience:
PREFIX arch: <https://meta.linked.archi/core#>
# Find the notation set for the "Architect" presentation context
SELECT ?notationSet WHERE {
?context skos:prefLabel "Architect"@en ;
arch:usesNotationSet ?notationSet .
}
If no context-specific set is found, fall back to arch:notationSet on the Metamodel.
For the full visual notation architecture, see the Visual Notation Guide.
Same Model, Different Notation — Vendor Icons vs ArchiMate¶
The NotationSet/PresentationContext system enables a powerful pattern: the same underlying model rendered with completely different visual styles depending on the audience.
## An architect sees standard ArchiMate notation (rounded rectangles + badges)
mypc:ArchitectContext
arch:usesNotationSet am4not:ArchiMate40StandardNotation .
## A developer sees AWS Architecture Icons (icon-centric, 64px service icons)
mypc:DeveloperContext
arch:usesNotationSet mynot:AWSIconNotation .
The AWS notation set uses arch-vis:IconCentric rendering mode — the icon IS the element:
:EC2Notation
a arch-vis:VisualNotation ;
arch-vis:notationFor myonto:EC2Instance ;
arch-vis:inNotationSet :AWSIconNotation ;
arch-vis:defaultStyle [
arch-vis:renderingMode arch-vis:IconCentric ;
arch-vis:iconSymbol "https://d1.awsstatic.com/icons/Arch_Amazon-EC2_64.svg"^^xsd:anyURI ;
arch-vis:iconPlacement arch-vis:Center ;
arch-vis:fillColor "#FFFFFF" ;
arch-vis:defaultWidth 64.0 ;
arch-vis:defaultHeight 64.0 ;
] .
The underlying RDF model data is identical in both views — only the visual rendering changes. This works for AWS, Azure, GCP, Kubernetes, or any vendor icon set. See the Visual Notation Guide — Vendor Icon Sets for the full pattern.
Filtering: Show Only Your Custom Types¶
If you extend ArchiMate 4.0 but only want to show your custom types in the palette (not all 55 ArchiMate types), filter by namespace:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?class ?label WHERE {
?class rdfs:subClassOf+ arch:Element ;
skos:prefLabel ?label .
FILTER(STRSTARTS(STR(?class), "https://meta.linked.archi/examples/cloudplatform/"))
}
Or show both, but visually separate them — custom types in the primary palette, inherited types in a collapsible "ArchiMate 4.0" section.
Palette Construction Conformance Specification¶
This section restates the palette behaviour as a normative specification. A tool that implements this section correctly will produce a conformant palette from any Linked.Archi metamodel without hardcoded knowledge of specific modeling languages.
Definitions¶
| Term | Meaning |
|---|---|
| Metamodel Manifest | An instance of arch:Metamodel that aggregates all semantic assets for a modeling language. |
| Concrete Class | An owl:Class that is rdfs:subClassOf+ either arch:Element or arch:QualifiedRelationship and does NOT carry the annotation arch:isAbstractClass true. |
| Abstract Class | An owl:Class annotated with arch:isAbstractClass true. These exist for inheritance and constraint checking only — they MUST NOT appear in the palette. |
| Palette Group | A skos:Concept in the taxonomy that has at least one skos:narrower link resolving (directly or transitively) to a Concrete Class. |
| Palette Entry | A single draggable item in the palette UI, corresponding to one Concrete Class. |
| Active NotationSet | The arch-vis:NotationSet resolved via the current PresentationContext (or the metamodel default). |
Inputs¶
A conformant palette builder MUST accept exactly three semantic assets, resolved from the Metamodel Manifest:
| Manifest Property | Asset Type | Provides |
|---|---|---|
arch:modelConcepts |
OWL Ontology (*-onto.ttl) |
Element and relationship classes with labels, definitions, and hierarchy |
arch:conceptClassification |
SKOS ConceptScheme (*-tax.ttl) |
Palette grouping and ordering via skos:narrower trees |
arch:notationSet (or via PresentationContext) |
NotationSet (*-notation.ttl) |
Shape, colour, icon, and rendering mode per concept |
Try the queries: Every SPARQL example below can be executed against live semantic assets in the SPARQL Playground — select a modeling language and click Execute. Each query has a ▶ Try it link that opens the playground pre-loaded with that query.
Algorithm¶
A conformant implementation MUST perform these steps in order. Steps 4 and 6 are OPTIONAL but RECOMMENDED.
Step 1 — Discover concrete element types.
Query all owl:Class instances that are rdfs:subClassOf+ arch:Element. Exclude any class where arch:isAbstractClass true is asserted. The arch:isAbstractClass annotation does NOT propagate through rdfs:subClassOf — only explicitly annotated classes are abstract.
Normative query:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?label ?definition WHERE {
?class rdfs:subClassOf+ arch:Element ;
skos:prefLabel ?label .
FILTER NOT EXISTS { ?class arch:isAbstractClass true }
OPTIONAL { ?class skos:definition ?definition }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
Example output (ArchiMate 3.2, first rows):
| ?class | ?label | ?definition |
|---|---|---|
am:BusinessActor |
"Business Actor" | "A business actor is a business entity that is capable of performing behavior." |
am:BusinessProcess |
"Business Process" | "A business process represents a sequence of business behaviors that achieves a specific outcome…" |
am:ApplicationComponent |
"Application Component" | "An encapsulation of application functionality aligned to implementation structure…" |
am:Node |
"Node" | "A computational or physical resource that hosts, manipulates, or interacts with other computational or physical resources." |
| … | … | … |
Classes like am:BehaviorElement, am:ActiveStructureElement, and am:BusinessLayerElement do NOT appear because they carry arch:isAbstractClass true.
Step 2 — Discover concrete relationship types.
Query all owl:Class instances that are rdfs:subClassOf arch:QualifiedRelationship. For each, resolve arch:unqualifiedForm to the predicate, then read arch:domainIncludes and arch:rangeIncludes to determine valid source and target types.
Normative query:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?label ?predicate ?sourceDomain ?targetRange WHERE {
?class rdfs:subClassOf arch:QualifiedRelationship ;
skos:prefLabel ?label ;
arch:unqualifiedForm ?predicate .
OPTIONAL { ?predicate arch:domainIncludes ?sourceDomain }
OPTIONAL { ?predicate arch:rangeIncludes ?targetRange }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
Example output (ArchiMate 3.2, selected rows):
| ?class | ?label | ?predicate | ?sourceDomain | ?targetRange |
|---|---|---|---|---|
am:Composition |
"Composition" | am:composedOf |
arch:Element |
arch:Element |
am:Serving |
"Serving" | am:serves |
arch:Element |
arch:Element |
am:Realization |
"Realization" | am:realizes |
arch:Element |
arch:Element |
am:Triggering |
"Triggering" | am:triggers |
am:BehaviorElement |
am:BehaviorElement |
| … | … | … | … | … |
A tool uses ?sourceDomain / ?targetRange to enable or disable connection handles on the canvas — e.g., Triggering is only valid between behaviour elements.
Step 3 — Build palette groups from the SKOS taxonomy.
Walk the skos:narrower tree starting from the top concepts of the ConceptScheme. Each skos:Concept with at least one descendant that resolves to a Concrete Class becomes a Palette Group. The skos:prefLabel of the concept is the group label.
Leaf nodes in the taxonomy reference OWL class IRIs (from Step 1) via skos:narrower. These are the Palette Entries within that group.
The canonical taxonomy pattern (used by all Linked.Archi modeling languages):
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix myonto: <https://meta.linked.archi/mymodel/onto#> .
@prefix : <https://meta.linked.archi/mymodel/tax#> .
## Group concept — becomes a palette section header
:BusinessLayer
a skos:Concept ;
skos:prefLabel "Business Layer"@en ;
skos:narrower myonto:BusinessActor, myonto:BusinessProcess,
myonto:BusinessService, myonto:BusinessObject ;
.
## The skos:narrower targets are OWL class IRIs from the ontology.
## A palette builder walks narrower to find what goes in each group.
Normative query for group membership (requires both the taxonomy and ontology loaded into the same graph):
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
# Convention: skos:narrower is used ONLY at the boundary from taxonomy concept → OWL class.
# Taxonomy-internal hierarchy uses skos:broader (child → parent) exclusively.
# Taxonomy provides: ?group (skos:Concept with skos:narrower → OWL class IRIs)
# Ontology provides: ?class a owl:Class, skos:prefLabel, arch:isAbstractClass
SELECT ?group ?groupLabel ?class ?classLabel WHERE {
?group a skos:Concept ;
skos:narrower ?class ;
skos:prefLabel ?groupLabel .
?class a owl:Class ;
skos:prefLabel ?classLabel .
FILTER NOT EXISTS { ?class arch:isAbstractClass true }
FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
FILTER(lang(?classLabel) = "en" || lang(?classLabel) = "")
}
ORDER BY ?groupLabel ?classLabel
To build a nested palette tree (e.g., "Element by Layer" → "Business Layer" → items), walk skos:broader upward from each group:
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
# Get the ancestor chain for palette nesting
SELECT ?group ?groupLabel ?parent ?parentLabel WHERE {
?group a skos:Concept ;
skos:broader ?parent ;
skos:prefLabel ?groupLabel .
?parent skos:prefLabel ?parentLabel .
FILTER(lang(?groupLabel) = "en" || lang(?groupLabel) = "")
FILTER(lang(?parentLabel) = "en" || lang(?parentLabel) = "")
}
Example output (ArchiMate 3.2, first rows):
| ?group | ?groupLabel | ?class | ?classLabel |
|---|---|---|---|
amtax:BusinessLayer |
"Business Layer" | am:BusinessActor |
"Business Actor" |
amtax:BusinessLayer |
"Business Layer" | am:BusinessCollaboration |
"Business Collaboration" |
amtax:BusinessLayer |
"Business Layer" | am:BusinessProcess |
"Business Process" |
amtax:ApplicationLayer |
"Application Layer" | am:ApplicationComponent |
"Application Component" |
amtax:ApplicationLayer |
"Application Layer" | am:DataObject |
"Data Object" |
amtax:TechnologyLayer |
"Technology Layer" | am:Node |
"Node" |
amtax:TechnologyLayer |
"Technology Layer" | am:Artifact |
"Artifact" |
| … | … | … | … |
This is the data needed to render a grouped palette: each ?groupLabel becomes a section header, each ?classLabel becomes a draggable item within that section.
Alternative linking: some metamodels additionally use rdfs:seeAlso from individual leaf concepts to OWL classes. A conformant tool MUST support both skos:narrower (group → class, the primary pattern) and rdfs:seeAlso (concept → class, legacy/supplementary). When both are present, skos:narrower takes precedence for building the palette tree.
Step 4 — Attach visual notation (RECOMMENDED).
For each Concrete Class, query the Active NotationSet for a arch-vis:VisualNotation where arch-vis:notationFor matches the class IRI. Extract the arch-vis:defaultStyle to obtain shape, colour, icon, and rendering mode.
Normative query:
PREFIX arch-vis: <https://meta.linked.archi/core-vis#>
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?class ?shape ?fillColor ?lineColor ?iconSymbol ?iconPlacement
?renderingMode ?width ?height ?compositeImage WHERE {
?vn a arch-vis:VisualNotation ;
arch-vis:notationFor ?class ;
arch-vis:inNotationSet ?activeNotationSet ;
arch-vis:defaultStyle ?style .
?style arch-vis:shapeType ?shape ;
arch-vis:fillColor ?fillColor .
OPTIONAL { ?style arch-vis:lineColor ?lineColor }
OPTIONAL { ?style arch-vis:iconSymbol ?iconSymbol }
OPTIONAL { ?style arch-vis:iconPlacement ?iconPlacement }
OPTIONAL { ?style arch-vis:renderingMode ?renderingMode }
OPTIONAL { ?style arch-vis:defaultWidth ?width }
OPTIONAL { ?style arch-vis:defaultHeight ?height }
OPTIONAL { ?vn arch:prefVisNotation ?compositeImage }
}
Example output (ArchiMate 3.2, selected rows):
| ?class | ?shape | ?fillColor | ?lineColor | ?iconSymbol | ?iconPlacement | ?renderingMode | ?width | ?height |
|---|---|---|---|---|---|---|---|---|
am:BusinessActor |
arch-vis:RoundedRectangle |
"#FFFFB5" | "#000000" | …/glyphs/business-actor.svg |
arch-vis:TopRight |
arch-vis:ShapeWithBadge |
120.0 | 55.0 |
am:ApplicationComponent |
arch-vis:RoundedRectangle |
"#C9E7CB" | "#000000" | …/glyphs/application-component.svg |
arch-vis:TopRight |
arch-vis:ShapeWithBadge |
120.0 | 55.0 |
am:Node |
arch-vis:RoundedRectangle |
"#C9E7CB" | "#000000" | …/glyphs/node.svg |
arch-vis:TopRight |
arch-vis:ShapeWithBadge |
120.0 | 55.0 |
| … | … | … | … | … | … | … | … | … |
This gives the tool everything it needs to render each palette entry: the geometric shape, fill/line colours, which glyph to badge in the corner, and the default dimensions.
Fallback: if no VisualNotation is found for a class, the tool SHOULD fall back to arch:prefVisNotation on the class itself (a single SVG image). If that is also absent, render a plain rectangle with the class label.
Step 5 — Resolve the Active NotationSet.
The Active NotationSet is determined as follows (in priority order):
- If a PresentationContext is active, use the value of
arch:usesNotationSeton that context. - Otherwise, use the value of
arch:notationSeton the Metamodel Manifest. - If neither is present, skip visual notation (palette entries render as plain text).
Step 6 — Apply viewpoint filter (OPTIONAL).
If a viewpoint is active, restrict the palette to classes listed in arch:includesConcept on the viewpoint. The tool MUST also include subclasses of listed classes (i.e., if the viewpoint includes am4:ApplicationComponent and cp:Microservice rdfs:subClassOf am4:ApplicationComponent, Microservice is allowed).
Conformance Requirements¶
A tool claiming conformance to this specification:
| ID | Requirement | Level |
|---|---|---|
| PAL-1 | MUST exclude classes annotated with arch:isAbstractClass true from the palette. |
Required |
| PAL-2 | MUST derive palette entries solely from owl:Class instances in the loaded ontology — no hardcoded type lists. |
Required |
| PAL-3 | MUST group palette entries according to the SKOS taxonomy hierarchy when a taxonomy is present. | Required |
| PAL-4 | MUST support both skos:narrower (concept → class) and rdfs:seeAlso (class → concept) linking patterns for group membership. |
Required |
| PAL-5 | SHOULD render palette entries using the Active NotationSet's arch-vis:defaultStyle when available. |
Recommended |
| PAL-6 | SHOULD support all three rendering modes (ShapeWithBadge, IconCentric, ShapeOnly). |
Recommended |
| PAL-7 | MUST support viewpoint-based palette filtering when a viewpoint is active. | Required |
| PAL-8 | MUST NOT require code changes to add new element types — adding a class to the ontology and taxonomy is sufficient. | Required |
| PAL-9 | MUST read relationship connectivity rules (arch:domainIncludes, arch:rangeIncludes) and expose them to the canvas for connection validation. |
Required |
| PAL-10 | SHOULD fall back gracefully when optional assets (notation, taxonomy) are absent — elements remain usable, just unstyled or ungrouped. | Recommended |
Verification¶
To verify conformance, load the ArchiMate 3.2 metamodel (modelingLanguages/archimate/3.2/archimate3.2-metamodel.ttl and its dependencies) and confirm:
- The palette contains exactly the concrete element types (no abstract classes like
BehaviorElementorStrategyLayerElement). - Elements are grouped by layer/aspect as defined in
archimate3.2-tax.ttl. - Each element entry displays the correct shape, fill colour, and icon glyph from
archimate3.2-notation.ttl. - Relationship types show correct line styles and endpoint decorations.
- Activating a viewpoint restricts the palette to only the concepts listed in that viewpoint.
Component 3: Canvas / Editor¶
The canvas is where users place elements and draw relationships. This is the most UI-intensive component and the one where technology choice matters most.
Recommended Libraries¶
| Library | Strengths | Best For |
|---|---|---|
| ReactFlow | React-native, handles, minimap, built-in layout | Web-based tools with React |
| Cytoscape.js | Graph-focused, many layouts, extensions | Graph-heavy tools |
| D3.js | Full control, force layouts | Custom visualizations |
| JointJS | Diagramming-focused, ports, links | Traditional diagramming tools |
Creating Model Data¶
When a user drags a Microservice from the palette onto the canvas, the tool creates RDF triples:
@prefix cp: <https://meta.linked.archi/examples/cloudplatform/onto#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix : <https://model.example.com/my-platform#> .
:OrderService a cp:Microservice ;
skos:prefLabel "Order Service"@en ;
cp:resilienceLevel "critical" ;
cp:deploymentStrategy "blue-green" ;
cp:serviceTier "tier-1" .
Creating Relationships¶
When a user draws an edge from OrderService to PaymentService, the tool creates both the direct triple and the qualified relationship:
# Direct triple — for graph traversal
:OrderService cp:dependsOn :PaymentService .
# Qualified relationship — for metadata and management
:OrderService cp:qualifiedDependsOn :dep-order-payment .
:dep-order-payment a cp:Dependency ;
arch:source :OrderService ;
arch:target :PaymentService ;
skos:prefLabel "Order → Payment dependency"@en .
Connection Validation¶
Before allowing a user to complete an edge, check whether the relationship type permits that source→target combination. Use the arch:domainIncludes / arch:rangeIncludes data from the palette query:
function canConnect(
sourceType: string,
targetType: string,
relationshipType: RelationshipType,
classHierarchy: Map<string, string[]> // class → superclasses
): boolean {
const sourceValid = relationshipType.validSources.some(
s => sourceType === s || classHierarchy.get(sourceType)?.includes(s)
);
const targetValid = relationshipType.validTargets.some(
t => targetType === t || classHierarchy.get(targetType)?.includes(t)
);
return sourceValid && targetValid;
}
This uses the class hierarchy — a Microservice is a valid source for any relationship that accepts ApplicationComponent, because cp:Microservice rdfs:subClassOf am4:ApplicationComponent.
Property Panel¶
When the user selects an element, show a property panel. The properties come from the ontology:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?property ?label ?range WHERE {
?property arch:domainIncludes ?elementType ;
skos:prefLabel ?label .
OPTIONAL { ?property rdfs:range ?range }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
For cp:Microservice, this returns resilienceLevel, deploymentStrategy, serviceTier — each with its datatype. The SHACL shapes provide the allowed values (sh:in) for dropdowns.
Extracting Allowed Values from SHACL¶
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?property ?allowedValue WHERE {
?shape sh:targetClass ?elementType ;
sh:property ?propShape .
?propShape sh:path ?property ;
sh:in/rdf:rest*/rdf:first ?allowedValue .
}
For cp:resilienceLevel on cp:Microservice, this returns: "critical", "high", "medium", "low" — ready for a dropdown.
Component 4: Live SHACL Validation¶
SHACL shapes turn the metamodel's rules into real-time feedback — red squiggles when a required property is missing, warnings when a relationship has an invalid target.
How It Works¶
- The user edits the model (adds an element, changes a property, draws an edge).
- The tool serializes the current model graph.
- The SHACL engine validates the model graph against the shapes graph.
- Validation results are mapped back to canvas elements as errors/warnings.
TypeScript: rdf-validate-shacl¶
import SHACLValidator from 'rdf-validate-shacl';
import { DataFactory } from 'n3';
async function validate(
dataStore: Store,
shapesStore: Store
): Promise<ValidationResult[]> {
const validator = new SHACLValidator(shapesStore, { factory: DataFactory });
const report = validator.validate(dataStore);
return report.results.map(r => ({
focusNode: r.focusNode?.value, // the element IRI
path: r.path?.value, // the property that failed
message: r.message?.[0]?.value, // human-readable message
severity: r.severity?.value, // sh:Violation, sh:Warning, sh:Info
sourceShape: r.sourceShape?.value, // which shape triggered this
}));
}
Python: pyshacl¶
from pyshacl import validate as shacl_validate
def validate(data_graph, shapes_graph):
conforms, results_graph, results_text = shacl_validate(
data_graph,
shacl_graph=shapes_graph,
inference='none',
abort_on_first=False,
)
# Parse results_graph for individual violations
return parse_validation_results(results_graph)
Mapping Results to the Canvas¶
Each validation result has a focusNode — the IRI of the element that failed. Map this back to the canvas node and show the error:
function applyValidationResults(
canvas: CanvasState,
results: ValidationResult[]
) {
// Clear previous markers
canvas.clearAllMarkers();
for (const result of results) {
const node = canvas.findNodeByIRI(result.focusNode);
if (!node) continue;
if (result.severity.endsWith('Violation')) {
node.addMarker('error', result.message);
} else if (result.severity.endsWith('Warning')) {
node.addMarker('warning', result.message);
} else {
node.addMarker('info', result.message);
}
}
}
When to Validate¶
- On every edit — if the model is small (< 500 elements). SHACL validation is fast for small graphs.
- On save — if the model is large. Debounce to avoid blocking the UI.
- On demand — a "Validate" button that runs the full check.
For the CI/CD pipeline, the existing .scripts/validate.sh handles batch validation using RDF4J:
What the Cloud Platform Shapes Catch¶
The cloudplatform-shapes.ttl enforces rules like:
- Every Microservice must have a
skos:prefLabel,resilienceLevel resilienceLevelmust be one of: critical, high, medium, lowdeploymentStrategymust be one of: blue-green, canary, rolling, recreate- Every Container must have a
containerImageand positivereplicaCount - Every Dependency must have
arch:sourceandarch:target - Critical microservices must be tier-1 (conditional rule)
Component 5: Viewpoint Filter¶
Viewpoints restrict which element and relationship types are visible in a given view. When the user opens a "Service Dependency View", only the relevant types appear in the palette and on the canvas.
Discovering Viewpoints¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?viewpoint ?label ?definition ?viewType WHERE {
?viewpoint a arch:Viewpoint ;
skos:prefLabel ?label .
OPTIONAL { ?viewpoint skos:definition ?definition }
OPTIONAL { ?viewpoint arch:viewType ?viewType }
FILTER(lang(?label) = "en" || lang(?label) = "")
}
For the Cloud Platform metamodel, this returns four viewpoints: Service Dependency, Deployment, Data Flow, Platform Overview.
Getting Allowed Concepts for a Viewpoint¶
PREFIX arch: <https://meta.linked.archi/core#>
SELECT ?concept WHERE {
<viewpoint-iri> arch:includesConcept ?concept .
}
The Service Dependency viewpoint includes: cp:Microservice, cp:APIGateway, am4:ApplicationComponent, cp:Dependency. Only these types appear in the palette when this viewpoint is active.
Filtering the Palette¶
function filterPalette(
fullPalette: PaletteGroup[],
allowedConcepts: Set<string>,
classHierarchy: Map<string, string[]>
): PaletteGroup[] {
return fullPalette
.map(group => ({
...group,
elements: group.elements.filter(el =>
allowedConcepts.has(el.iri) ||
classHierarchy.get(el.iri)?.some(parent => allowedConcepts.has(parent))
),
}))
.filter(group => group.elements.length > 0);
}
Filtering the Canvas¶
Elements already on the canvas that don't belong to the active viewpoint can be dimmed or hidden:
function applyViewpointFilter(
canvas: CanvasState,
allowedConcepts: Set<string>
) {
for (const node of canvas.nodes) {
const isAllowed = allowedConcepts.has(node.type) ||
classHierarchy.get(node.type)?.some(p => allowedConcepts.has(p));
node.setOpacity(isAllowed ? 1.0 : 0.2);
}
}
Viewpoint Metadata for the UI¶
Viewpoints also carry stakeholder and concern information — useful for a viewpoint picker:
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?viewpoint ?vpLabel ?stakeholder ?shLabel ?concern ?cLabel WHERE {
?viewpoint a arch:Viewpoint ;
skos:prefLabel ?vpLabel .
OPTIONAL {
?viewpoint arch:targetsStakeholder ?stakeholder .
?stakeholder skos:prefLabel ?shLabel .
}
OPTIONAL {
?viewpoint arch:viewpointFramesConcern ?concern .
?concern skos:prefLabel ?cLabel .
}
}
This lets you build a viewpoint picker that says: "Service Dependency View — for Developers and Architects — addresses Dependency Management concern."
Component 6: Document Generator¶
The document generation pipeline is fully declarative — the ontology defines what to extract, how to structure it, and how to render it. The tool contains no domain-specific logic.
For the full specification, see the Deliverable Generator Spec. This section shows how to implement it.
The Pipeline¶
Step 1: Discover Template Sections¶
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?section ?order ?title ?resultKey ?query WHERE {
<template-iri> arch:templateHasSection ?section .
?section arch:sectionOrder ?order ;
skos:prefLabel ?title ;
arch:sectionResultKey ?resultKey ;
arch:sectionQuery ?query .
}
ORDER BY ?order
Step 2: Execute Queries and Assemble Context¶
import Handlebars from 'handlebars';
async function generateDocument(
templateIRI: string,
store: Store,
meta: Record<string, string>
): Promise<string> {
// 1. Get sections
const sections = await querySections(store, templateIRI);
// 2. Execute each section's SPARQL query
const context: Record<string, any> = { meta };
for (const section of sections) {
const results = await executeSPARQL(store, section.query);
context[section.resultKey] = results;
}
// 3. Load and render the Handlebars template
const templatePath = await getTemplateResource(store, templateIRI);
const templateSource = await fs.readFile(templatePath, 'utf-8');
const template = Handlebars.compile(templateSource);
return template(context);
}
Step 3: Render¶
The Cloud Platform metamodel includes two deliverable templates:
Service Catalog — lists all microservices with their properties:
# Service Catalog
Generated: {{meta.date}}
## Microservices
| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
{{#each microservices}}
| {{this.label}} | {{this.resilience}} | {{this.deployment}} | {{this.tier}} |
{{/each}}
## Dependencies
| Source | Target | Description |
|--------|--------|-------------|
{{#each dependencies}}
| {{this.sourceLabel}} | {{this.targetLabel}} | {{this.description}} |
{{/each}}
Deployment Guide — infrastructure topology:
# Deployment Guide
## Kubernetes Clusters
{{#each clusters}}
### {{this.label}}
- Region: {{this.region}}
- Node count: {{this.nodeCount}}
#### Containers
| Container | Image | Replicas |
|-----------|-------|----------|
{{#each this.containers}}
| {{this.label}} | {{this.image}} | {{this.replicas}} |
{{/each}}
{{/each}}
Output Formats¶
The arch:templateHasFormat property declares supported formats. For Markdown → HTML, use a Markdown renderer. For Markdown → PDF, pipe through Pandoc or WeasyPrint:
# Markdown → HTML
npx marked service-catalog.md -o service-catalog.html
# Markdown → PDF
pandoc service-catalog.md -o service-catalog.pdf
Component 7: AI Agent Access (MCP)¶
The Model Context Protocol (MCP) exposes the knowledge graph to AI agents. The Linked.Archi MCP server (ai-enablement/linked-archi-mcp/) provides a reference implementation using TypeScript + Oxigraph.
What the MCP Server Exposes¶
| Tool | What it does |
|---|---|
query-graph |
Execute arbitrary SPARQL against the model |
list-elements |
List all elements with type and label |
get-element |
Get full details for one element |
get-relationships |
Get all relationships for an element |
graph-stats |
Summary statistics (element counts by type, relationship counts) |
| Prompt | What it does |
|---|---|
impact-analysis |
Trace how a change to one element propagates |
architecture-overview |
Generate a natural-language summary of the model |
explore-ontology |
Explain the metamodel structure |
Building Your Own MCP Server¶
If your modeling tool has its own graph store, you can expose it via MCP:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new McpServer({
name: 'my-architecture-tool',
version: '1.0.0',
});
server.tool('query-graph', { query: z.string() }, async ({ query }) => {
const results = await sparqlEngine.query(query);
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});
server.tool('list-elements', {}, async () => {
const results = await sparqlEngine.query(`
PREFIX arch: <https://meta.linked.archi/core#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?element ?label ?type WHERE {
?element a ?type ; skos:prefLabel ?label .
?type rdfs:subClassOf+ arch:Element .
}
`);
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});
const transport = new StdioServerTransport();
await server.connect(transport);
Why This Matters¶
With MCP, an AI agent can:
- "What microservices depend on the Payment Service?" →
query-graphwith a SPARQL query - "Show me all critical services without a deployment strategy" →
query-graphwith a SHACL-like filter - "Generate an impact analysis for removing the Order Database" →
impact-analysisprompt - "What's the overall health of this architecture?" →
architecture-overviewprompt
The agent queries the same graph the tool uses — no separate export or sync needed.
Presentation Contexts: Stakeholder-Specific Rendering¶
Presentation contexts are SKOS concepts that define how the same model looks for different audiences. They are not a separate component — they influence how components 2, 3, 5, and 6 behave.
How They Work¶
The metamodel declares a arch:presentationContextScheme — a SKOS ConceptScheme with concepts like Executive, Architect, Developer, Operations, Governance.
Each context implies:
| Aspect | Executive | Developer | Architect |
|---|---|---|---|
| Palette | Business elements only | Application + Technology | All domains |
| Detail level | Labels only | Full properties | Full properties + relationships |
| Viewpoints | Strategy, Capability | Service Dependency, Deployment | All viewpoints |
| Documents | Executive Summary | Service Catalog, Deployment Guide | Architecture Definition |
| Visual style | Simplified icons, large labels | Technical icons, code-style | Full ArchiMate notation |
Querying Presentation Contexts¶
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?context ?label ?definition WHERE {
?context skos:topConceptOf ?scheme ;
skos:prefLabel ?label ;
skos:definition ?definition .
FILTER(lang(?label) = "en" || lang(?label) = "")
}
Applying a Context¶
The tool reads the context's skos:definition to determine filtering rules. A more structured approach adds custom properties to each context:
:DeveloperContext
a skos:Concept ;
skos:prefLabel "Developer / Engineering"@en ;
arch:contextIncludesDomain :ApplicationDomain, :TechnologyDomain ;
arch:contextDefaultViewpoint :ServiceDependencyViewpoint ;
arch:contextDetailLevel "full" ;
arch:contextDeliverableTemplate :ServiceCatalog, :DeploymentGuide .
The tool queries these properties and adjusts the palette, default viewpoint, and available documents accordingly.
Putting It All Together: The Minimal Viable Tool¶
Here is the simplest possible tool — a CLI that loads a metamodel, validates a model, and generates a document:
// minimal-tool.ts
import { Store, Parser } from 'n3';
import SHACLValidator from 'rdf-validate-shacl';
import Handlebars from 'handlebars';
import { QueryEngine } from '@comunica/query-sparql';
async function main() {
const store = new Store();
const parser = new Parser();
const engine = new QueryEngine();
// 1. Load metamodel + model
const files = [
'core/core-onto.ttl',
'examples/custom-metamodel/cloudplatform-onto.ttl',
'examples/custom-metamodel/cloudplatform-metamodel.ttl',
'examples/custom-metamodel/cloudplatform-shapes.ttl',
'examples/custom-metamodel/cloudplatform-deliverable-templates.ttl',
'examples/custom-metamodel/example-model.ttl',
];
for (const file of files) {
const ttl = await Bun.file(file).text();
store.addQuads(parser.parse(ttl));
}
// 2. Validate
const shapesStore = new Store();
const shapesTtl = await Bun.file(
'examples/custom-metamodel/cloudplatform-shapes.ttl'
).text();
shapesStore.addQuads(parser.parse(shapesTtl));
const validator = new SHACLValidator(shapesStore);
const report = validator.validate(store);
if (!report.conforms) {
console.error('Validation errors:');
for (const r of report.results) {
console.error(` ${r.focusNode?.value}: ${r.message?.[0]?.value}`);
}
} else {
console.log('Model is valid.');
}
// 3. Generate document
const queryResult = await engine.queryBindings(`
PREFIX cp: <https://meta.linked.archi/examples/cloudplatform/onto#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?label ?resilience ?deployment ?tier WHERE {
?ms a cp:Microservice ;
skos:prefLabel ?label ;
cp:resilienceLevel ?resilience .
OPTIONAL { ?ms cp:deploymentStrategy ?deployment }
OPTIONAL { ?ms cp:serviceTier ?tier }
} ORDER BY ?label
`, { sources: [store] });
const microservices = (await queryResult.toArray()).map(b => ({
label: b.get('label')?.value,
resilience: b.get('resilience')?.value,
deployment: b.get('deployment')?.value ?? '—',
tier: b.get('tier')?.value ?? '—',
}));
const template = Handlebars.compile(`# Service Catalog
| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
{{#each microservices}}
| {{this.label}} | {{this.resilience}} | {{this.deployment}} | {{this.tier}} |
{{/each}}
`);
const output = template({ microservices });
console.log(output);
}
main();
Run it:
Output:
# Service Catalog
| Service | Resilience | Deployment | Tier |
|---------|-----------|------------|------|
| API Gateway | high | blue-green | tier-1 |
| Inventory Service | medium | rolling | tier-2 |
| Order Service | critical | blue-green | tier-1 |
| Payment Service | critical | canary | tier-1 |
| Shipping Service | medium | rolling | tier-2 |
From here, add a web UI (ReactFlow for the canvas, a sidebar for the palette, a panel for properties) and you have a visual modeling tool.
Incremental Build Path¶
| Phase | Components | Effort | What You Get |
|---|---|---|---|
| 1. CLI | Loader + Validator + Generator | 1–2 days | Validate models and generate documents from the command line |
| 2. Browser | + Palette + Canvas (ReactFlow) | 1–2 weeks | Visual editor with drag-and-drop elements and edges |
| 3. Smart Editor | + Viewpoint Filter + Property Panel | 1 week | Viewpoint-filtered palette, property editing with dropdowns from SHACL |
| 4. Full Tool | + Presentation Contexts + MCP | 1 week | Stakeholder-specific views and AI agent integration |
Each phase produces a usable tool. You don't need to build everything before shipping.
How This Relates to Existing Linked.Archi Tools¶
The Linked.Archi toolbox already implements several of these components:
| Existing Tool | Components It Implements |
|---|---|
| Validation Script (Java + RDF4J) | SHACL Validator (batch mode) |
| Deliverable Generator | Document Generator (full pipeline spec) |
A new modeling tool can reuse these directly or use them as reference implementations.
References¶
- Build Your Own Metamodel — The prerequisite: creating the semantic assets
- Cloud Platform Walkthrough — The worked metamodel example used throughout this guide
- EDGY Modeling Guide — Authoring EDGY enterprise design models with Linked.Archi (worked example: Meridian Insurance Group)
- ArchiMate 4.0 Modeling Guide — Authoring ArchiMate 4.0 models with Linked.Archi (worked example: NordFreight Logistics)
- TOGAF Modeling Guide — Using the TOGAF Enterprise Metamodel with Linked.Archi (worked example: Atlas Global Bank)
- Deliverable Templates Guide — The SPARQL + Handlebars generation pipeline
- Deliverable Generator Spec — Full tool specification for document generation
- Relationship Modeling Guide — The three-declaration pattern for relationships
- Design Decisions — DD-2 (OWL+SHACL), DD-10 (qualified relationships), DD-13 (metamodel as manifest)
- Validation Guide — SHACL validation pipeline
- Semantic Architecture as Code — The Turtle-in-Git workflow
- ReactFlow — React library for node-based editors
- Comunica — Client-side SPARQL engine
- Oxigraph — Rust-based RDF store with SPARQL and WASM support
- rdf-validate-shacl — JavaScript SHACL validator
- pyshacl — Python SHACL validator
- Model Context Protocol — Standard for AI agent tool integration