Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efc4078e83 | ||
|
|
ef251352c6 | ||
|
|
900e0986e6 | ||
|
|
d9777fb90e | ||
|
|
21770c241d | ||
|
|
f9bc461361 | ||
|
|
887ca973a0 | ||
|
|
6daf70fef3 | ||
|
|
35adfb480c |
No files matched your search
@@ -1,7 +1,11 @@
|
||||
# archify-vscode-ext
|
||||
|
||||
[](https://buymeacoffee.com/root.at.skic)
|
||||
|
||||
Agent Skill for Archify diagrams and models as read by the [Archify Diagram Viewer](https://marketplace.visualstudio.com/items?itemName=root-at-skic.archify-vscode-ext) VS Code extension: how to write `.archify` diagrams (architecture, workflow, sequence, dataflow, lifecycle), how to put them together as a model (`main.archify`, shared elements in `*.archify-registry` files, `ref:` components, `diagram:` links, C4-style folders), and tasks that validate, list, scaffold, resolve and render them. The Archify validator and renderer (MIT, by tt-a1i) are bundled under `scripts/vendor`, so nothing else needs to be installed.
|
||||
|
||||
How the skill and an agent keep a model together, with examples: [articles on Medium](https://medium.com/@root.at.skic).
|
||||
|
||||
## Install and update
|
||||
|
||||
One command, any agent (Claude Code, Codex, Copilot, Cline, …):
|
||||
@@ -14,7 +18,7 @@ npx skills update --global
|
||||
In Claude Code, the same skill is also a plugin of the `root-at-skic` marketplace. Every commit on `main` is a new version, so an update always brings the latest; switch on auto-update for the marketplace and it arrives on its own:
|
||||
|
||||
```
|
||||
/plugin marketplace add https://gitea.lego-cloud.eu/global-skills/marketplace.git
|
||||
/plugin marketplace add https://gitea.lego-cloud.eu/vscode-extensions-skills/marketplace.git
|
||||
/plugin install archify-vscode-ext@root-at-skic
|
||||
```
|
||||
|
||||
@@ -26,6 +30,7 @@ Quick start:
|
||||
cd scripts
|
||||
task model:scaffold -- --dir="/path/to/online-shop" --id="online-shop" --name="Online Shop" --c4
|
||||
task model:validate -- --dir="/path/to/online-shop"
|
||||
task model:graph -- --dir="/path/to/online-shop" --query="from:web to:db"
|
||||
task diagram:render -- --file="/path/to/online-shop/0200-containers/online-shop.archify" --out="/tmp/containers.html"
|
||||
```
|
||||
|
||||
@@ -35,4 +40,6 @@ Requires Node.js 18+, bash and [go-task](https://taskfile.dev). The skill target
|
||||
|
||||
## Support
|
||||
|
||||
If this saves you time, you can buy me a coffee: https://buymeacoffee.com/root.at.skic
|
||||
If this saves you time, a coffee keeps it going:
|
||||
|
||||
[](https://buymeacoffee.com/root.at.skic)
|
||||
@@ -15,7 +15,7 @@ license: MIT
|
||||
compatibility: Requires Node.js 18+, bash and go-task (task). No network access; Archify is bundled under scripts/vendor.
|
||||
metadata:
|
||||
author: root-at-skic
|
||||
version: "1.0"
|
||||
version: "1.3"
|
||||
origin-repository: https://gitea.lego-cloud.eu/vscode-extensions-skills/archify-vscode-ext.git
|
||||
targets: Archify Diagram Viewer (root-at-skic.archify-vscode-ext)
|
||||
---
|
||||
@@ -39,8 +39,9 @@ task --list-all
|
||||
|---|---|
|
||||
| `task model:validate -- --dir="<model or file inside>"` | Registry, every diagram (refs resolved, Archify validator), every `diagram:` link. Exit 1 on any error. Works on a single diagram outside a model too. |
|
||||
| `task model:list -- --dir="<model>"` | What the model page shows: metadata, diagrams by folder, registry elements with "used in N diagrams", decisions with status and the diagrams that reference them. |
|
||||
| `task model:graph -- --dir="<model>" [--query="uses:api"] [--links]` | The model as one graph, as the extension's **Graph** page shows it: every element, every connection once, plus a node per decision and per registry file. `--query` narrows it with the page's language: `database`, `type:backend`, `diagram:0200-*`, `decision:<id>`, `uses:api`, `near:api,2`, `from:web to:db` (shortest path), `unused`, `shared`, `-shared`; terms AND together, a leading `-` excludes. Use it to answer "what talks to X", "how does A reach B", "what does this decision touch", "which registry elements are unused". |
|
||||
| `task model:scaffold -- --dir="<folder>" --id="<id>" --name="<name>" [--description="…"] [--c4]` | Creates `main.archify`; with `--c4` also `registry/` and the `0100-context`, `0200-containers`, `0300-components` folders with starter diagrams. Never overwrites. |
|
||||
| `task model:scaffold -- --dir="<folder>" --example="<name>" [--id="<id>"] [--name="<name>"] [--description="…"]` | Copies a complete example model: `c4-standard-web-app`, `c4-e-shop` or `c4-event-based-integration` (the same three the extension offers under *Archify \| New Model…*). Id, name and description default to the example's own. Never overwrites. |
|
||||
| `task model:scaffold -- --dir="<folder>" --example="<name>" [--id="<id>"] [--name="<name>"] [--description="…"]` | Copies a complete example model: C4 `c4-standard-web-app`, `c4-e-shop`, `c4-event-based-integration`; arc42 `arc42-cinema-tickets`, `arc42-iot-telemetry`, `arc42-team-wiki`; ArchiMate `archimate-insurance-claims`, `archimate-hr-onboarding`, `archimate-logistics-tracking` (the same nine the extension offers under *Archify \| New Model…*). Id, name and description default to the example's own. Never overwrites. |
|
||||
| `task model:resolve -- --file="<diagram>" [--out="<file.json>"]` | The Archify JSON the extension hands to Archify, for the plain Archify CLI or CI. |
|
||||
| `task diagram:validate -- --file="<diagram>"` | One diagram through Archify (refs resolved when inside a model). |
|
||||
| `task diagram:render -- --file="<diagram>" --out="<file.html>"` | Standalone HTML page for people without VS Code. |
|
||||
@@ -174,8 +175,11 @@ Diagrams next to `main.archify` are listed first, ungrouped; deeper ones by fold
|
||||
- **Boxes need clear gaps.** Components closer than 8px fail; labels wider than the box fail (`6.5px` per character plus 13). The grid in `references/authoring.md` keeps a standard layout clear.
|
||||
- **Links are relative to the model root**, never to the file, and must end in `.archify` and exist. `..` is refused.
|
||||
- **A second `main.archify` deeper down** is reported, not nested. One model per tree.
|
||||
- **Diagram kinds other than architecture** take `ref` too (workflow and dataflow `nodes`, sequence `participants`, lifecycle `states`), but the registry `type` must fit that kind: lifecycle states use their own type enum, and sequence participants have no `tag`.
|
||||
- **Diagram kinds other than architecture** take `ref` too (workflow and dataflow `nodes`, sequence `participants`, lifecycle `states`), but the registry `type` must fit that kind: lifecycle states use their own type enum, and sequence participants have no `tag`. The extension resolves these since 0.11.0; older versions showed such a diagram with schema errors.
|
||||
- **Tags are not drawn on the box.** Archify shows `tag` only in the focus panel and search. When a type must be visible (an ArchiMate element type, a C4 level), put it in `sublabel` too.
|
||||
- **Decisions are files, ids are words.** One `*.archify-decision` per decision (`decision:` block with id, title, status, date, elements, diagrams, supersedes; prose in context, decision, consequences). Diagrams point at them with `decisions: [<id>]` on the diagram, a node or an edge; the resolver strips the key. An unquoted numeric id (`0001`) is refused because YAML reads it as 1. When an agent changes the shape of a model (a new store, a new queue, a new boundary), it writes a decision and links the elements it touched.
|
||||
- **The graph answers structure questions.** Before guessing from file contents, run `task model:graph` with a query: `uses:<id>` for neighbours, `from:<a> to:<b>` for the route, `decision:<id>` for what a decision touches, `unused` for registry elements no diagram draws. Walks follow the diagrams' connections only; decision and registry nodes follow the elements they link to. The extension's Graph page (the **Model | Graph | Source** switch on `main.archify`) shows the same graph with the same queries.
|
||||
- **The extension checks keys the skill does not.** Since extension 0.9.2 every Archify page has a **Source** view (Diagram | Graph | Source, View | Source on registries and decisions) that checks the YAML against the bundled JSON schemas without a YAML extension: an unknown key in a registry element (`lable:`) or a diagram is an error there. `task model:validate` does not check registry keys yet and passes such a file, so read registry edits back for typos. When a user says "the Source view shows an error", the message names the key and path (`Unknown key "lable" in elements.api.`).
|
||||
- **Descriptions stay in the registry.** The extension shows an element's description as a tooltip in its Elements panel; nothing is generated into the diagram. Write `cards:` in a diagram only when you want text blocks below it.
|
||||
- **No em dashes or smart quotes in ids**; ids match `^[a-zA-Z][a-zA-Z0-9_-]*$`.
|
||||
|
||||
@@ -183,5 +187,5 @@ Diagrams next to `main.archify` are listed first, ungrouped; deeper ones by fold
|
||||
|
||||
- Read `references/authoring.md` when writing or repairing a diagram: kinds, fields, grid, labels, boundaries, brands, validator codes and fixes.
|
||||
- Read `references/formats.md` when writing `main.archify`, a registry, a `ref` or a link, or when unsure what the tasks strip before Archify runs.
|
||||
- Read `references/folders.md` when laying out a model folder or explaining how the extension finds the root and groups diagrams.
|
||||
- `examples/c4-standard-web-app/`, `examples/c4-e-shop/` and `examples/c4-event-based-integration/` are complete C4-style models that validate with 0 problems; copy their shape, or copy one with `model:scaffold --example`.
|
||||
- Read `references/folders.md` when laying out a model folder or explaining how the extension finds the root and groups diagrams; it has the C4, arc42 and ArchiMate layouts.
|
||||
- `examples/` holds nine complete models that validate with 0 problems; copy their shape, or copy one with `model:scaffold --example`. Three C4 (`c4-*`: context, containers, components). Three arc42 (`arc42-*`: the overview is section 3 business context with goals, constraints, quality, risks and glossary as `cards`; `0300-context`, `0500-building-blocks`, `0600-runtime` sequence diagrams, `0700-deployment`; decisions are section 9). Three ArchiMate (`archimate-*`: the overview is a layered view in three region boundaries; `0100-business`, `0200-application`, `0300-technology`; one registry file per layer; each element's ArchiMate type as its `tag` and `sublabel`, since Archify shows tags only in the focus panel).
|
||||
@@ -0,0 +1,39 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Technical context
|
||||
output: technical-context.html
|
||||
components:
|
||||
- ref: customer
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: usher
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: tickets
|
||||
pos: [290, 160]
|
||||
size: [150, 60]
|
||||
- ref: psp
|
||||
pos: [560, 60]
|
||||
size: [150, 60]
|
||||
- ref: mail
|
||||
pos: [560, 160]
|
||||
size: [150, 60]
|
||||
- ref: pos
|
||||
pos: [560, 260]
|
||||
size: [150, 60]
|
||||
connections:
|
||||
- { from: customer, to: tickets, label: HTTPS JSON, variant: emphasis }
|
||||
- { from: usher, to: tickets, label: HTTPS app }
|
||||
- { from: tickets, to: psp, label: REST webhook, variant: security }
|
||||
- { from: tickets, to: mail, label: SMTP TLS }
|
||||
- { from: tickets, to: pos, label: REST VPN }
|
||||
cards:
|
||||
- dot: cyan
|
||||
title: 3 Domain interfaces
|
||||
items:
|
||||
- "Moviegoer: in shows, seats, payment start; out seat map, QR tickets"
|
||||
- "Usher: in scanned QR codes; out valid or invalid, seat"
|
||||
- "Payment provider: out checkout session; in paid or failed webhook"
|
||||
- "E-mail service: out ticket e-mails with QR and receipt"
|
||||
- "Box-office POS: in counter sales; out seat availability"
|
||||
@@ -0,0 +1,49 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Booking API - level 2
|
||||
output: booking.html
|
||||
decisions: [seat-holds-with-ttl]
|
||||
components:
|
||||
- ref: web
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- id: programme
|
||||
type: backend
|
||||
label: Programme
|
||||
sublabel: shows, halls
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- id: seats
|
||||
type: backend
|
||||
label: Seat Holds
|
||||
sublabel: lock, release
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- id: checkout
|
||||
type: backend
|
||||
label: Checkout
|
||||
sublabel: orders, webhook
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: seatlock
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
decisions: [seat-holds-with-ttl]
|
||||
- ref: db
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: psp
|
||||
pos: [460, 360]
|
||||
size: [150, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Booking API
|
||||
wraps: [programme, seats, checkout]
|
||||
connections:
|
||||
- { from: web, to: programme, label: browse }
|
||||
- { from: web, to: seats, label: hold, variant: emphasis }
|
||||
- { from: web, to: checkout, label: pay }
|
||||
- { from: programme, to: db, label: SQL }
|
||||
- { from: seats, to: seatlock, label: SET NX, decisions: [seat-holds-with-ttl] }
|
||||
- { from: checkout, to: psp, label: session, variant: security }
|
||||
@@ -0,0 +1,66 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Building blocks - level 1
|
||||
output: level-1.html
|
||||
decisions: [booking-owns-seat-inventory]
|
||||
components:
|
||||
- ref: web
|
||||
pos: [40, 110]
|
||||
size: [130, 60]
|
||||
- ref: mobile
|
||||
pos: [40, 210]
|
||||
size: [130, 60]
|
||||
- ref: scanner
|
||||
pos: [40, 360]
|
||||
size: [130, 60]
|
||||
- ref: booking
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: seatlock
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: db
|
||||
pos: [460, 260]
|
||||
size: [130, 60]
|
||||
- ref: ticketing
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- ref: possync
|
||||
pos: [670, 260]
|
||||
size: [130, 60]
|
||||
- ref: psp
|
||||
pos: [900, 160]
|
||||
size: [150, 60]
|
||||
- ref: pos
|
||||
pos: [900, 260]
|
||||
size: [150, 60]
|
||||
- ref: mail
|
||||
pos: [900, 460]
|
||||
size: [150, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Cinema Tickets
|
||||
wraps: [web, mobile, scanner, booking, seatlock, db, ticketing, possync]
|
||||
connections:
|
||||
- { from: web, to: booking, label: JSON, variant: emphasis }
|
||||
- { from: mobile, to: booking, label: JSON }
|
||||
- { from: booking, to: seatlock, label: hold }
|
||||
- { from: booking, to: db, label: SQL }
|
||||
- { from: booking, to: psp, label: checkout, variant: security }
|
||||
- { from: booking, to: ticketing, label: issue, labelAt: [315, 234] }
|
||||
- { from: scanner, to: ticketing, label: check-in }
|
||||
- { from: ticketing, to: db, label: SQL }
|
||||
- { from: ticketing, to: mail, label: QR mail }
|
||||
- { from: possync, to: db, label: SQL }
|
||||
- { from: possync, to: pos, label: sales, decisions: [booking-owns-seat-inventory] }
|
||||
cards:
|
||||
- dot: amber
|
||||
title: 8 Crosscutting concepts
|
||||
items:
|
||||
- "Persistence: PostgreSQL is the truth; Redis only for holds"
|
||||
- "Security: OIDC for staff apps; card data never stored"
|
||||
- "Errors: idempotency keys; failed webhooks are retried"
|
||||
- "Times in UTC; shown in cinema local time"
|
||||
- "QR payload signed with an Ed25519 key"
|
||||
- "Structured logs with order id as trace key"
|
||||
@@ -0,0 +1,27 @@
|
||||
schema_version: 1
|
||||
diagram_type: sequence
|
||||
meta:
|
||||
title: Runtime - book seats
|
||||
output: book-seats.html
|
||||
viewBox: [1060, 680]
|
||||
column_fit: spread
|
||||
decisions: [seat-holds-with-ttl]
|
||||
participants:
|
||||
- { ref: customer }
|
||||
- { ref: web }
|
||||
- { ref: booking }
|
||||
- { ref: seatlock }
|
||||
- { ref: psp }
|
||||
- { ref: ticketing }
|
||||
- { ref: mail }
|
||||
messages:
|
||||
- { from: customer, to: web, y: 170, label: pick seats, variant: emphasis }
|
||||
- { from: web, to: booking, y: 214, label: POST /holds }
|
||||
- { from: booking, to: seatlock, y: 258, label: SET NX TTL 8m, decisions: [seat-holds-with-ttl] }
|
||||
- { from: seatlock, to: booking, y: 302, label: OK, variant: return }
|
||||
- { from: booking, to: web, y: 346, label: hold id, variant: return }
|
||||
- { from: web, to: psp, y: 390, label: hosted checkout, variant: security }
|
||||
- { from: psp, to: booking, y: 434, label: webhook paid, variant: security }
|
||||
- { from: booking, to: ticketing, y: 478, label: issue tickets }
|
||||
- { from: ticketing, to: mail, y: 522, label: QR e-mail }
|
||||
- { from: mail, to: customer, y: 566, label: tickets, variant: return }
|
||||
@@ -0,0 +1,19 @@
|
||||
schema_version: 1
|
||||
diagram_type: sequence
|
||||
meta:
|
||||
title: Runtime - check-in at the door
|
||||
output: check-in.html
|
||||
viewBox: [900, 480]
|
||||
column_fit: spread
|
||||
decisions: [signed-qr-codes]
|
||||
participants:
|
||||
- { ref: usher }
|
||||
- { ref: scanner }
|
||||
- { ref: ticketing }
|
||||
- { ref: db }
|
||||
messages:
|
||||
- { from: usher, to: scanner, y: 180, label: scan QR, variant: emphasis, note: signature checked offline, decisions: [signed-qr-codes] }
|
||||
- { from: scanner, to: ticketing, y: 230, label: POST /checkins }
|
||||
- { from: ticketing, to: db, y: 280, label: first scan wins }
|
||||
- { from: ticketing, to: scanner, y: 330, label: valid seat 7F, variant: return }
|
||||
- { from: scanner, to: usher, y: 380, label: green light, variant: return }
|
||||
@@ -0,0 +1,74 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Deployment - production
|
||||
output: production.html
|
||||
components:
|
||||
- ref: customer
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: mobile
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- id: ingress
|
||||
type: security
|
||||
label: Ingress
|
||||
sublabel: TLS, WAF
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: web
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: booking
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: ticketing
|
||||
pos: [460, 260]
|
||||
size: [130, 60]
|
||||
- ref: possync
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
- ref: seatlock
|
||||
pos: [710, 110]
|
||||
size: [130, 60]
|
||||
- ref: db
|
||||
pos: [710, 260]
|
||||
size: [130, 60]
|
||||
- ref: pos
|
||||
pos: [250, 460]
|
||||
size: [150, 60]
|
||||
- ref: scanner
|
||||
pos: [40, 460]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: EU cloud - k8s cluster
|
||||
wraps: [ingress, web, booking, ticketing, possync]
|
||||
- kind: security-group
|
||||
label: Managed data zone
|
||||
wraps: [seatlock, db]
|
||||
- kind: region
|
||||
label: Customer phones
|
||||
wraps: [mobile]
|
||||
- kind: region
|
||||
label: Cinema site
|
||||
wraps: [pos, scanner]
|
||||
connections:
|
||||
- { from: customer, to: ingress, label: HTTPS, variant: emphasis }
|
||||
- { from: ingress, to: web, label: route }
|
||||
- { from: ingress, to: booking, label: route }
|
||||
- { from: booking, to: seatlock, label: TLS }
|
||||
- { from: booking, to: db, label: TLS }
|
||||
- { from: ticketing, to: db, label: TLS }
|
||||
- { from: possync, to: pos, label: VPN, variant: security }
|
||||
- { from: scanner, to: ingress, label: HTTPS }
|
||||
- { from: mobile, to: ingress, label: HTTPS }
|
||||
cards:
|
||||
- dot: slate
|
||||
title: 7 Mapping
|
||||
items:
|
||||
- "Web Shop, Booking API, Ticket Service, POS Sync -> EU k8s cluster"
|
||||
- "Seat Locks -> managed Redis; Tickets DB -> managed PostgreSQL"
|
||||
- "Mobile App -> customer phones"
|
||||
- "Scanner App -> door phones at the cinema site"
|
||||
- "Box-office POS -> cinema site, VPN to POS Sync"
|
||||
@@ -0,0 +1,75 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Quality tree
|
||||
output: quality-tree.html
|
||||
components:
|
||||
- id: quality
|
||||
type: cloud
|
||||
label: Quality
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- id: reliability
|
||||
type: backend
|
||||
label: Reliability
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- id: performance
|
||||
type: backend
|
||||
label: Performance
|
||||
pos: [250, 210]
|
||||
size: [130, 60]
|
||||
- id: availability
|
||||
type: backend
|
||||
label: Availability
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- id: security
|
||||
type: security
|
||||
label: Security
|
||||
pos: [250, 460]
|
||||
size: [130, 60]
|
||||
- id: s-nodouble
|
||||
type: external
|
||||
label: No double sale
|
||||
pos: [460, 10]
|
||||
size: [150, 60]
|
||||
- id: s-payfail
|
||||
type: external
|
||||
label: Failed pay frees seats
|
||||
pos: [460, 110]
|
||||
size: [170, 60]
|
||||
- id: s-peak
|
||||
type: external
|
||||
label: Checkout < 2 s at peak
|
||||
pos: [460, 210]
|
||||
size: [170, 60]
|
||||
- id: s-offline
|
||||
type: external
|
||||
label: Offline door scans
|
||||
pos: [460, 360]
|
||||
size: [170, 60]
|
||||
- id: s-forged
|
||||
type: external
|
||||
label: Forged QR rejected
|
||||
pos: [460, 460]
|
||||
size: [170, 60]
|
||||
connections:
|
||||
- { from: quality, to: reliability }
|
||||
- { from: quality, to: performance }
|
||||
- { from: quality, to: availability }
|
||||
- { from: quality, to: security }
|
||||
- { from: reliability, to: s-nodouble }
|
||||
- { from: reliability, to: s-payfail }
|
||||
- { from: performance, to: s-peak }
|
||||
- { from: availability, to: s-offline }
|
||||
- { from: security, to: s-forged }
|
||||
cards:
|
||||
- dot: emerald
|
||||
title: 10 Quality scenarios
|
||||
items:
|
||||
- "500 holds on one show in 1 min: no seat sold twice"
|
||||
- "Payment fails or is abandoned: seats free again within 8 min"
|
||||
- "Friday 19:00 peak, 300 checkouts/min: p95 under 2 s"
|
||||
- "Wi-Fi down at the door: scans go on, check-ins sync later"
|
||||
- "Edited or forged QR code scanned: rejected at once"
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: seat-holds-with-ttl
|
||||
title: Seat holds as Redis keys with a TTL
|
||||
status: accepted
|
||||
date: 2026-09-08
|
||||
elements: [booking, seatlock]
|
||||
diagrams: [0500-building-blocks/booking.archify, 0600-runtime/book-seats.archify]
|
||||
context: >
|
||||
A customer may spend minutes at the payment page. Holding seats
|
||||
as rows in the database left orphans when a browser closed, and
|
||||
a nightly job freed them far too late for Friday shows.
|
||||
decision: >
|
||||
A hold is one Redis key per seat, set with SET NX and an
|
||||
8-minute TTL. The payment webhook turns holds into an order in
|
||||
PostgreSQL; an expired key frees the seat without any job.
|
||||
consequences: >
|
||||
Holds survive no Redis restart; at worst a customer re-picks
|
||||
seats. The webhook must check the hold still exists before it
|
||||
confirms the order, otherwise it refunds.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: booking-owns-seat-inventory
|
||||
title: The Booking API owns the seat inventory
|
||||
status: accepted
|
||||
date: 2026-09-22
|
||||
elements: [booking, db, possync, pos]
|
||||
diagrams: [0500-building-blocks/level-1.archify]
|
||||
context: >
|
||||
The box-office POS and the web shop both sold seats from their
|
||||
own copy of the seat map. Twice a month a seat was sold at the
|
||||
counter and online.
|
||||
decision: >
|
||||
The Tickets DB is the only seat inventory. The POS asks POS Sync
|
||||
for a seat before it prints a ticket; POS Sync writes the sale
|
||||
through the same rules as online orders.
|
||||
consequences: >
|
||||
The counter needs the network to sell; a fallback block of
|
||||
reserved seats per show covers outages. The POS vendor adapter
|
||||
is ours to maintain.
|
||||
@@ -0,0 +1,19 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: signed-qr-codes
|
||||
title: Signed QR codes checked offline at the door
|
||||
status: accepted
|
||||
date: 2026-10-06
|
||||
elements: [ticketing, scanner]
|
||||
diagrams: [0600-runtime/check-in.archify]
|
||||
context: >
|
||||
Cinema Wi-Fi drops when a full hall arrives. A scanner that
|
||||
needs the server for every ticket stops the queue.
|
||||
decision: >
|
||||
The Ticket Service signs each QR payload (ticket id, show,
|
||||
seat) with a private key. The scanner app verifies it with the
|
||||
public key and queues check-ins until it is online again.
|
||||
consequences: >
|
||||
A forwarded ticket scans twice while offline; the second scan
|
||||
is flagged when the queue syncs. Key rotation needs an app
|
||||
update window.
|
||||
@@ -0,0 +1,91 @@
|
||||
schema_version: 1
|
||||
model:
|
||||
id: cinema-tickets
|
||||
name: Cinema Tickets
|
||||
description: >
|
||||
An arc42 example: seat booking for the fictional Lantern Cinemas.
|
||||
1 Requirements, quality goals, stakeholders; 2 Constraints;
|
||||
4 Solution strategy; 11 Risks; 12 Glossary: cards on this overview.
|
||||
3 Context: this overview (business) and 0300-context (technical).
|
||||
5 Building blocks: 0500-building-blocks, black boxes in registry/.
|
||||
6 Runtime: 0600-runtime. 7 Deployment: 0700-deployment.
|
||||
8 Crosscutting concepts: card on 0500-building-blocks/level-1.
|
||||
9 Decisions: decisions/. 10 Quality: 1000-quality.
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Cinema Tickets - business context
|
||||
output: main.html
|
||||
components:
|
||||
- ref: customer
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: usher
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: tickets
|
||||
pos: [280, 160]
|
||||
size: [150, 60]
|
||||
- ref: psp
|
||||
pos: [540, 60]
|
||||
size: [150, 60]
|
||||
- ref: mail
|
||||
pos: [540, 160]
|
||||
size: [150, 60]
|
||||
- ref: pos
|
||||
pos: [540, 260]
|
||||
size: [150, 60]
|
||||
connections:
|
||||
- { from: customer, to: tickets, label: books seats, variant: emphasis }
|
||||
- { from: usher, to: tickets, label: checks in }
|
||||
- { from: tickets, to: psp, label: payments, variant: security }
|
||||
- { from: tickets, to: mail, label: tickets }
|
||||
- { from: tickets, to: pos, label: seat sales }
|
||||
cards:
|
||||
- dot: orange
|
||||
title: 1.1 Requirements
|
||||
items:
|
||||
- "Browse shows and pick seats on a seat map"
|
||||
- "Hold seats, pay online, get QR tickets by e-mail"
|
||||
- "Ushers check tickets in at the door"
|
||||
- "Box-office POS sells from the same seats"
|
||||
- dot: cyan
|
||||
title: 1.2 Quality goals
|
||||
items:
|
||||
- "No seat is ever sold twice"
|
||||
- "Checkout under 2 s at Friday peak"
|
||||
- "Door check-in works offline"
|
||||
- dot: amber
|
||||
title: 1.3 Stakeholders
|
||||
items:
|
||||
- "Moviegoer: quick booking, tickets on the phone"
|
||||
- "Usher: fast scans even without Wi-Fi"
|
||||
- "Box-office staff: one seat map with online sales"
|
||||
- "Lantern Cinemas management: fewer refunds, low cost"
|
||||
- dot: slate
|
||||
title: 2 Constraints
|
||||
items:
|
||||
- "Existing box-office POS stays"
|
||||
- "Card data only at the provider"
|
||||
- "Team of four; hosted in the EU"
|
||||
- dot: emerald
|
||||
title: 4 Solution strategy
|
||||
items:
|
||||
- "Node.js, PostgreSQL, Redis on managed EU k8s: fits a team of four"
|
||||
- "Three services by job: booking, tickets, POS sync"
|
||||
- "No double sale: Booking API sole seat owner (booking-owns-seat-inventory)"
|
||||
- "Checkout speed: Redis holds with TTL (seat-holds-with-ttl)"
|
||||
- "Offline check-in: signed QR codes (signed-qr-codes)"
|
||||
- dot: rose
|
||||
title: 11 Risks and technical debt
|
||||
items:
|
||||
- "Risk: POS vendor API is slow and undocumented"
|
||||
- "Risk: forwarded QR e-mails, first scan wins"
|
||||
- "Debt: POS Sync adapter has no automated tests"
|
||||
- "Debt: QR key rotation needs an app release"
|
||||
- dot: violet
|
||||
title: 12 Glossary
|
||||
items:
|
||||
- "Show: one screening of a film in a hall"
|
||||
- "Hold: a seat locked for one customer"
|
||||
- "Check-in: first valid scan of a ticket"
|
||||
- "POS: point-of-sale till at the box office"
|
||||
@@ -0,0 +1,72 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
web:
|
||||
type: frontend
|
||||
label: Web Shop
|
||||
sublabel: React
|
||||
brand: react
|
||||
description: >
|
||||
Responsibility: programme, seat map and checkout in the browser.
|
||||
Interfaces: provides the web UI; requires Booking API (JSON)
|
||||
and the provider's hosted checkout page.
|
||||
mobile:
|
||||
type: frontend
|
||||
label: Mobile App
|
||||
sublabel: iOS and Android
|
||||
description: >
|
||||
Responsibility: the web shop's booking flow plus a ticket wallet.
|
||||
Interfaces: provides the iOS/Android UI; requires Booking API
|
||||
(JSON).
|
||||
scanner:
|
||||
type: frontend
|
||||
label: Scanner App
|
||||
sublabel: door phones
|
||||
description: >
|
||||
Responsibility: reads QR codes at the door, checks signatures
|
||||
offline. Interfaces: provides the usher UI; requires Ticket
|
||||
Service (POST /checkins), queued while offline.
|
||||
booking:
|
||||
type: backend
|
||||
label: Booking API
|
||||
sublabel: Node.js
|
||||
brand: node-js
|
||||
description: >
|
||||
Responsibility: shows, seat maps, holds and orders; the only
|
||||
writer of the seat inventory. Interfaces: provides REST JSON to
|
||||
the apps and the payment webhook; requires Seat Locks, Tickets
|
||||
DB, payment provider, Ticket Service.
|
||||
diagram: 0500-building-blocks/booking.archify
|
||||
ticketing:
|
||||
type: backend
|
||||
label: Ticket Service
|
||||
sublabel: QR, check-in
|
||||
description: >
|
||||
Responsibility: issues signed QR tickets, mails them and records
|
||||
check-ins. Interfaces: provides issue and POST /checkins;
|
||||
requires Tickets DB and the e-mail service (SMTP).
|
||||
possync:
|
||||
type: backend
|
||||
label: POS Sync
|
||||
sublabel: adapter
|
||||
description: >
|
||||
Responsibility: exchanges seat sales with the box-office POS.
|
||||
Interfaces: provides a REST adapter to the POS over VPN;
|
||||
requires Tickets DB (SQL).
|
||||
seatlock:
|
||||
type: database
|
||||
label: Seat Locks
|
||||
sublabel: Redis
|
||||
brand: redis
|
||||
description: >
|
||||
Responsibility: short-lived seat holds with a TTL while the
|
||||
customer pays. Interfaces: provides Redis SET NX/GET/DEL to the
|
||||
Booking API; requires nothing.
|
||||
db:
|
||||
type: database
|
||||
label: Tickets DB
|
||||
sublabel: PostgreSQL
|
||||
brand: postgresql
|
||||
description: >
|
||||
Responsibility: shows, seats, orders and tickets; the source of
|
||||
truth. Interfaces: provides SQL to Booking API, Ticket Service
|
||||
and POS Sync; requires nothing.
|
||||
@@ -0,0 +1,14 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
customer:
|
||||
type: external
|
||||
label: Moviegoer
|
||||
sublabel: web and mobile
|
||||
description: >
|
||||
Picks a show, holds seats and pays; gets the tickets by e-mail.
|
||||
usher:
|
||||
type: external
|
||||
label: Usher
|
||||
sublabel: door staff
|
||||
description: >
|
||||
Scans tickets at the auditorium door with the scanner app.
|
||||
@@ -0,0 +1,30 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
tickets:
|
||||
type: cloud
|
||||
label: Cinema Tickets
|
||||
sublabel: the system
|
||||
description: >
|
||||
Online booking, seat holds, e-mail tickets and door check-in
|
||||
for the five cinemas of the fictional Lantern Cinemas chain.
|
||||
diagram: 0500-building-blocks/level-1.archify
|
||||
psp:
|
||||
type: external
|
||||
label: Payment provider
|
||||
sublabel: hosted checkout
|
||||
description: >
|
||||
Takes card and wallet payments on its own page; we never see
|
||||
card numbers.
|
||||
pos:
|
||||
type: external
|
||||
label: Box-office POS
|
||||
sublabel: in each cinema
|
||||
description: >
|
||||
Till system at the counter; sells walk-in seats from the same
|
||||
seat map.
|
||||
mail:
|
||||
type: external
|
||||
label: E-mail service
|
||||
sublabel: transactional
|
||||
description: >
|
||||
Delivers ticket e-mails with QR codes and receipts.
|
||||
@@ -0,0 +1,45 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Technical context
|
||||
output: technical-context.html
|
||||
decisions: [mqtt-with-mutual-tls]
|
||||
components:
|
||||
- ref: homeowner
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: ops-engineer
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: installer
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: telemetry-platform
|
||||
pos: [280, 160]
|
||||
size: [150, 60]
|
||||
- ref: heat-pump
|
||||
pos: [540, 60]
|
||||
size: [130, 60]
|
||||
- ref: push-service
|
||||
pos: [540, 160]
|
||||
size: [130, 60]
|
||||
- ref: service-desk
|
||||
pos: [540, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: homeowner, to: telemetry-platform, label: HTTPS }
|
||||
- { from: ops-engineer, to: telemetry-platform, label: HTTPS, variant: security }
|
||||
- { from: installer, to: telemetry-platform, label: HTTPS }
|
||||
- { from: heat-pump, to: telemetry-platform, label: MQTT 8883, variant: emphasis, decisions: [mqtt-with-mutual-tls] }
|
||||
- { from: telemetry-platform, to: push-service, label: HTTP/2 }
|
||||
- { from: telemetry-platform, to: service-desk, label: webhook }
|
||||
cards:
|
||||
- dot: cyan
|
||||
title: 3 Domain interfaces
|
||||
items:
|
||||
- "Heat pump: in telemetry and fault codes; out none"
|
||||
- "Homeowner: in app requests; out status, history, alerts"
|
||||
- "Ops engineer: in queries; out fleet dashboards"
|
||||
- "Installer: in device serial; out device certificate"
|
||||
- "Push service: out alert notifications"
|
||||
- "Service desk: out tickets for faults needing a visit"
|
||||
@@ -0,0 +1,50 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Ingest, level 2
|
||||
output: ingest.html
|
||||
components:
|
||||
- ref: mqtt-broker
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- id: decoder
|
||||
type: backend
|
||||
label: Decoder
|
||||
sublabel: payload v1, v2
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- id: validator
|
||||
type: backend
|
||||
label: Validator
|
||||
sublabel: ranges, clock
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- id: writer
|
||||
type: backend
|
||||
label: Batch writer
|
||||
sublabel: 500 rows
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
- id: dead-letter
|
||||
type: database
|
||||
label: Dead letters
|
||||
sublabel: rejected readings
|
||||
pos: [460, 310]
|
||||
size: [130, 60]
|
||||
- ref: tsdb
|
||||
pos: [880, 160]
|
||||
size: [130, 60]
|
||||
- ref: rules
|
||||
pos: [670, 310]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Ingest
|
||||
wraps: [decoder, validator, writer, dead-letter]
|
||||
connections:
|
||||
- { from: mqtt-broker, to: decoder, label: messages, variant: emphasis }
|
||||
- { from: decoder, to: validator, label: readings, variant: emphasis }
|
||||
- { from: validator, to: writer, label: valid, variant: emphasis }
|
||||
- { from: validator, to: dead-letter, label: invalid, variant: dashed, labelAt: [525, 234] }
|
||||
- { from: writer, to: tsdb, label: COPY, variant: emphasis }
|
||||
- { from: writer, to: rules, label: stream, labelAt: [735, 234] }
|
||||
@@ -0,0 +1,80 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Building blocks, level 1
|
||||
output: level-1.html
|
||||
decisions: [timescaledb-for-telemetry]
|
||||
components:
|
||||
- ref: heat-pump
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: installer
|
||||
pos: [40, 360]
|
||||
size: [130, 60]
|
||||
- ref: mqtt-broker
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
decisions: [mqtt-with-mutual-tls]
|
||||
- ref: provisioning
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- ref: rules
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: ingest
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: device-registry
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
- ref: tsdb
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
- ref: device-api
|
||||
pos: [670, 360]
|
||||
size: [130, 60]
|
||||
- ref: ops-dashboard
|
||||
pos: [880, 160]
|
||||
size: [130, 60]
|
||||
- ref: customer-app
|
||||
pos: [880, 360]
|
||||
size: [130, 60]
|
||||
- ref: push-service
|
||||
pos: [1090, 60]
|
||||
size: [130, 60]
|
||||
- ref: ops-engineer
|
||||
pos: [1090, 160]
|
||||
size: [130, 60]
|
||||
- ref: homeowner
|
||||
pos: [1090, 360]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: IoT Telemetry
|
||||
wraps: [mqtt-broker, provisioning, rules, ingest, device-registry, tsdb, device-api, ops-dashboard, customer-app]
|
||||
connections:
|
||||
- { from: heat-pump, to: mqtt-broker, label: publish, variant: emphasis, decisions: [mqtt-with-mutual-tls] }
|
||||
- { from: mqtt-broker, to: ingest, label: subscribe, variant: emphasis }
|
||||
- { from: ingest, to: tsdb, label: write, variant: emphasis }
|
||||
- { from: ingest, to: rules, label: stream }
|
||||
- { from: ingest, to: device-registry, label: lookup, labelAt: [525, 234] }
|
||||
- { from: rules, to: push-service, label: alert }
|
||||
- { from: installer, to: provisioning, label: register }
|
||||
- { from: provisioning, to: mqtt-broker, label: certs, variant: security }
|
||||
- { from: provisioning, to: device-registry, label: store }
|
||||
- { from: device-api, to: tsdb, label: read }
|
||||
- { from: device-api, to: device-registry, label: read }
|
||||
- { from: tsdb, to: ops-dashboard, label: query }
|
||||
- { from: customer-app, to: device-api, label: REST }
|
||||
- { from: ops-engineer, to: ops-dashboard, label: views }
|
||||
- { from: homeowner, to: customer-app, label: uses }
|
||||
cards:
|
||||
- dot: amber
|
||||
title: 8 Crosscutting concepts
|
||||
items:
|
||||
- Device identity, one X.509 certificate per device
|
||||
- Security, OIDC login for people, mTLS for devices
|
||||
- Persistence, PostgreSQL dialect for all stores
|
||||
- Idempotent writes, keyed by device id and timestamp
|
||||
- Error handling, invalid readings to dead letters
|
||||
- Observability, metrics and traces on every service
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
diagram_type: sequence
|
||||
meta:
|
||||
title: Device provisioning
|
||||
output: device-provisioning.html
|
||||
decisions: [mqtt-with-mutual-tls]
|
||||
participants:
|
||||
- { ref: installer }
|
||||
- { ref: provisioning }
|
||||
- { ref: device-registry, label: Devices }
|
||||
- { ref: heat-pump }
|
||||
- { ref: mqtt-broker }
|
||||
messages:
|
||||
- { from: installer, to: provisioning, y: 160, label: register serial, variant: emphasis }
|
||||
- { from: provisioning, to: device-registry, y: 210, label: store device }
|
||||
- { from: provisioning, to: installer, y: 260, label: certificate, variant: return }
|
||||
- { from: installer, to: heat-pump, y: 310, label: load certificate }
|
||||
- { from: heat-pump, to: mqtt-broker, y: 360, label: connect mTLS, variant: emphasis }
|
||||
- { from: mqtt-broker, to: provisioning, y: 410, label: check revocation }
|
||||
- { from: mqtt-broker, to: heat-pump, y: 460, label: CONNACK, variant: return }
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
diagram_type: sequence
|
||||
meta:
|
||||
title: Fault reading to alert
|
||||
output: telemetry-to-alert.html
|
||||
participants:
|
||||
- { ref: heat-pump }
|
||||
- { ref: mqtt-broker }
|
||||
- { ref: ingest }
|
||||
- { ref: tsdb, label: TSDB }
|
||||
- { ref: rules }
|
||||
- { ref: push-service }
|
||||
messages:
|
||||
- { from: heat-pump, to: mqtt-broker, y: 160, label: publish fault, variant: emphasis }
|
||||
- { from: mqtt-broker, to: ingest, y: 210, label: deliver QoS 1 }
|
||||
- { from: ingest, to: tsdb, y: 260, label: write batch }
|
||||
- { from: tsdb, to: ingest, y: 310, label: committed, variant: return }
|
||||
- { from: ingest, to: mqtt-broker, y: 360, label: ack, variant: return }
|
||||
- { from: ingest, to: rules, y: 410, label: reading }
|
||||
- { from: rules, to: push-service, y: 460, label: send alert, variant: emphasis }
|
||||
@@ -0,0 +1,67 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Deployment, production
|
||||
output: production.html
|
||||
decisions: [timescaledb-for-telemetry]
|
||||
components:
|
||||
- ref: heat-pump
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: mqtt-broker
|
||||
pos: [280, 160]
|
||||
size: [130, 60]
|
||||
- ref: ingest
|
||||
pos: [490, 160]
|
||||
size: [130, 60]
|
||||
- ref: rules
|
||||
pos: [490, 60]
|
||||
size: [130, 60]
|
||||
- ref: provisioning
|
||||
pos: [280, 260]
|
||||
size: [130, 60]
|
||||
- ref: device-api
|
||||
pos: [490, 260]
|
||||
size: [130, 60]
|
||||
- ref: tsdb
|
||||
pos: [760, 160]
|
||||
size: [130, 60]
|
||||
- ref: device-registry
|
||||
pos: [760, 260]
|
||||
size: [130, 60]
|
||||
- ref: ops-dashboard
|
||||
pos: [1030, 160]
|
||||
size: [130, 60]
|
||||
- ref: customer-app
|
||||
pos: [490, 440]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Kubernetes cluster, eu-central
|
||||
wraps: [mqtt-broker, ingest, rules, provisioning, device-api]
|
||||
- kind: security-group
|
||||
label: Managed DB zone
|
||||
wraps: [tsdb, device-registry]
|
||||
- kind: region
|
||||
label: Ops network, managed Grafana
|
||||
wraps: [ops-dashboard]
|
||||
- kind: region
|
||||
label: Homeowner phones
|
||||
wraps: [customer-app]
|
||||
connections:
|
||||
- { from: heat-pump, to: mqtt-broker, label: "8883", variant: emphasis }
|
||||
- { from: mqtt-broker, to: ingest, label: in-cluster }
|
||||
- { from: ingest, to: tsdb, label: "5432", variant: emphasis }
|
||||
- { from: ingest, to: rules, label: gRPC }
|
||||
- { from: device-api, to: device-registry, label: "5432" }
|
||||
- { from: tsdb, to: ops-dashboard, label: read-only }
|
||||
- { from: customer-app, to: device-api, label: HTTPS }
|
||||
cards:
|
||||
- dot: slate
|
||||
title: 7 Mapping
|
||||
items:
|
||||
- "mqtt-broker, ingest, rules -> Kubernetes cluster, eu-central"
|
||||
- "provisioning, device-api -> Kubernetes cluster, eu-central"
|
||||
- "tsdb, device-registry -> managed DB zone, eu-central"
|
||||
- "ops-dashboard -> ops network, managed Grafana"
|
||||
- "customer-app -> homeowner phones, app stores"
|
||||
@@ -0,0 +1,75 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Quality tree
|
||||
output: quality-tree.html
|
||||
components:
|
||||
- id: quality
|
||||
type: cloud
|
||||
label: Quality
|
||||
pos: [40, 240]
|
||||
size: [130, 100]
|
||||
- id: reliability
|
||||
type: backend
|
||||
label: Reliability
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- id: performance
|
||||
type: backend
|
||||
label: Performance
|
||||
pos: [250, 210]
|
||||
size: [130, 60]
|
||||
- id: security
|
||||
type: security
|
||||
label: Security
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- id: operability
|
||||
type: backend
|
||||
label: Operability
|
||||
pos: [250, 460]
|
||||
size: [130, 60]
|
||||
- id: q-no-loss
|
||||
type: frontend
|
||||
label: Broker restart, no loss
|
||||
pos: [460, 60]
|
||||
size: [180, 60]
|
||||
- id: q-alert-60s
|
||||
type: frontend
|
||||
label: Fault alert in 60 s
|
||||
pos: [460, 160]
|
||||
size: [180, 60]
|
||||
- id: q-peak-load
|
||||
type: frontend
|
||||
label: 4,000 readings/s
|
||||
pos: [460, 260]
|
||||
size: [180, 60]
|
||||
- id: q-revoke
|
||||
type: frontend
|
||||
label: Revoke cert in 15 min
|
||||
pos: [460, 360]
|
||||
size: [180, 60]
|
||||
- id: q-new-device
|
||||
type: frontend
|
||||
label: New device in 10 min
|
||||
pos: [460, 460]
|
||||
size: [180, 60]
|
||||
connections:
|
||||
- { from: quality, to: reliability }
|
||||
- { from: quality, to: performance }
|
||||
- { from: quality, to: security }
|
||||
- { from: quality, to: operability }
|
||||
- { from: reliability, to: q-no-loss }
|
||||
- { from: performance, to: q-alert-60s }
|
||||
- { from: performance, to: q-peak-load }
|
||||
- { from: security, to: q-revoke }
|
||||
- { from: operability, to: q-new-device }
|
||||
cards:
|
||||
- dot: emerald
|
||||
title: 10 Quality scenarios
|
||||
items:
|
||||
- "Broker restarts under load: devices reconnect, no accepted reading lost"
|
||||
- "Heat pump sends a fault code: homeowner gets a push within 60 s"
|
||||
- "Fleet peaks at 4,000 readings/s: p95 ingest lag stays under 5 s"
|
||||
- "A device certificate is stolen: ops revoke it, broker refuses it within 15 min"
|
||||
- "Installer registers a new heat pump: data visible in the app within 10 min"
|
||||
@@ -0,0 +1,21 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: mqtt-with-mutual-tls
|
||||
title: MQTT with mutual TLS for device traffic
|
||||
status: accepted
|
||||
date: 2026-08-12
|
||||
elements: [heat-pump, mqtt-broker, provisioning]
|
||||
diagrams: [0300-context/technical-context.archify, 0500-building-blocks/level-1.archify]
|
||||
context: >
|
||||
Heat pumps sit behind home routers on slow and flaky links.
|
||||
The first prototype polled an HTTPS endpoint and drained
|
||||
mobile data plans; shared API keys leaked from one device.
|
||||
decision: >
|
||||
Devices publish over MQTT 3.1.1 on port 8883. Each device
|
||||
has its own client certificate issued by Provisioning;
|
||||
the broker rejects any connection without one.
|
||||
consequences: >
|
||||
Small messages and one long-lived connection per device.
|
||||
Certificates must be rotated and revoked, so Provisioning
|
||||
is a critical building block. Installers need a step to
|
||||
register the device before it can connect.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: timescaledb-for-telemetry
|
||||
title: TimescaleDB for telemetry
|
||||
status: accepted
|
||||
date: 2026-09-03
|
||||
elements: [tsdb, ingest, ops-dashboard]
|
||||
diagrams: [0500-building-blocks/level-1.archify, 0700-deployment/production.archify]
|
||||
context: >
|
||||
The fleet sends about 4,000 readings a second. A plain
|
||||
PostgreSQL table grew too fast and dashboards slowed down
|
||||
after a few weeks of data.
|
||||
decision: >
|
||||
Store telemetry in TimescaleDB hypertables with hourly
|
||||
continuous aggregates. Raw data is kept 90 days, rollups
|
||||
10 years. The team already runs PostgreSQL.
|
||||
consequences: >
|
||||
SQL stays the query language for the API and the
|
||||
dashboard. Retention jobs must be monitored. Very high
|
||||
device counts may need sharding later (see risk R1).
|
||||
@@ -0,0 +1,97 @@
|
||||
schema_version: 1
|
||||
model:
|
||||
id: iot-telemetry
|
||||
name: IoT Telemetry
|
||||
description: >
|
||||
An arc42 example: a fictional heat pump maker collects device telemetry
|
||||
over MQTT, stores it, raises alerts and shows it to operations and customers.
|
||||
Where the 12 arc42 sections live:
|
||||
1 introduction and goals, 2 constraints, 4 solution strategy,
|
||||
11 risks and 12 glossary are cards on this overview;
|
||||
3 business context is this overview, technical context in 0300-context;
|
||||
5 building blocks in 0500-building-blocks (black boxes in the registry);
|
||||
6 runtime in 0600-runtime; 7 deployment in 0700-deployment;
|
||||
8 crosscutting concepts a card on 0500-building-blocks/level-1;
|
||||
9 decisions in decisions/; 10 quality tree in 1000-quality.
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Business context
|
||||
output: main.html
|
||||
components:
|
||||
- ref: homeowner
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: ops-engineer
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: installer
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: telemetry-platform
|
||||
pos: [280, 160]
|
||||
size: [150, 60]
|
||||
- ref: heat-pump
|
||||
pos: [540, 60]
|
||||
size: [130, 60]
|
||||
- ref: push-service
|
||||
pos: [540, 160]
|
||||
size: [130, 60]
|
||||
- ref: service-desk
|
||||
pos: [540, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: homeowner, to: telemetry-platform, label: checks }
|
||||
- { from: ops-engineer, to: telemetry-platform, label: monitors }
|
||||
- { from: installer, to: telemetry-platform, label: registers }
|
||||
- { from: heat-pump, to: telemetry-platform, label: telemetry, variant: emphasis }
|
||||
- { from: telemetry-platform, to: push-service, label: alerts }
|
||||
- { from: telemetry-platform, to: service-desk, label: tickets }
|
||||
cards:
|
||||
- dot: orange
|
||||
title: 1.1 Requirements
|
||||
items:
|
||||
- Collect telemetry from every heat pump every 30 s
|
||||
- Alert homeowner and ops on fault codes
|
||||
- Show live and historic data in app and dashboard
|
||||
- Register and provision new devices on site
|
||||
- dot: cyan
|
||||
title: 1.2 Quality goals
|
||||
items:
|
||||
- Reliability, no telemetry lost once accepted
|
||||
- Timeliness, alert within 60 s of a fault
|
||||
- Security, every device authenticated
|
||||
- dot: emerald
|
||||
title: 1.3 Stakeholders
|
||||
items:
|
||||
- "Homeowner: sees status, gets alerts quickly"
|
||||
- "Ops engineer: fleet view, few false alarms"
|
||||
- "Installer: register a device in minutes"
|
||||
- "Firmware partner: stable MQTT contract"
|
||||
- dot: slate
|
||||
title: 2 Constraints
|
||||
items:
|
||||
- Devices speak MQTT only; firmware by a partner
|
||||
- Data stays in one EU region
|
||||
- Team of six; PostgreSQL skills in house
|
||||
- dot: amber
|
||||
title: 4 Solution strategy
|
||||
items:
|
||||
- MQTT with mutual TLS for devices (security, ADR 0001)
|
||||
- TimescaleDB for telemetry, SQL everywhere (ADR 0002)
|
||||
- Ingest acks only after commit (reliability)
|
||||
- Rules engine on the ingest stream (timeliness)
|
||||
- Split by flow, ingest vs read vs provisioning
|
||||
- dot: rose
|
||||
title: 11 Risks and technical debt
|
||||
items:
|
||||
- "Risk: fleet growth beyond one TSDB node"
|
||||
- "Risk: partner firmware delays cert rotation"
|
||||
- "Debt: payload v1 decoder kept for old firmware"
|
||||
- "Debt: rules configured in code, no UI"
|
||||
- dot: violet
|
||||
title: 12 Glossary
|
||||
items:
|
||||
- Telemetry, readings a device sends
|
||||
- Rollup, hourly aggregate of telemetry
|
||||
- Provisioning, giving a device its identity
|
||||
- Fault code, error number from the device
|
||||
@@ -0,0 +1,75 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
mqtt-broker:
|
||||
type: messagebus
|
||||
label: MQTT broker
|
||||
sublabel: mutual TLS
|
||||
description: >
|
||||
Responsibility: terminates device connections, one topic per device.
|
||||
Interfaces: provides MQTT 8883 with mTLS; requires certs and
|
||||
revocation checks from Provisioning.
|
||||
ingest:
|
||||
type: backend
|
||||
label: Ingest
|
||||
sublabel: Go
|
||||
brand: go
|
||||
description: >
|
||||
Responsibility: decodes, validates and stores telemetry.
|
||||
Interfaces: requires MQTT subscription, TSDB, device registry;
|
||||
provides the reading stream to the rules engine.
|
||||
diagram: 0500-building-blocks/ingest.archify
|
||||
tsdb:
|
||||
type: database
|
||||
label: Time series DB
|
||||
sublabel: TimescaleDB
|
||||
brand: postgresql
|
||||
description: >
|
||||
Responsibility: raw telemetry 90 days, hourly rollups 10 years.
|
||||
Interfaces: provides SQL on 5432 to Device API and dashboard;
|
||||
requires batched writes from Ingest.
|
||||
rules:
|
||||
type: backend
|
||||
label: Rules engine
|
||||
sublabel: alerting
|
||||
description: >
|
||||
Responsibility: evaluates thresholds and fault codes, raises alerts.
|
||||
Interfaces: requires the reading stream from Ingest;
|
||||
provides alerts to the push service and service desk.
|
||||
provisioning:
|
||||
type: security
|
||||
label: Provisioning
|
||||
sublabel: device certs
|
||||
description: >
|
||||
Responsibility: registers devices and issues client certificates.
|
||||
Interfaces: provides a REST register call for installers and
|
||||
revocation checks for the broker; requires the device registry.
|
||||
device-registry:
|
||||
type: database
|
||||
label: Device registry
|
||||
sublabel: PostgreSQL
|
||||
brand: postgresql
|
||||
description: >
|
||||
Responsibility: devices, owners, firmware and certificate state.
|
||||
Interfaces: provides SQL on 5432 to Ingest, Provisioning, Device API.
|
||||
device-api:
|
||||
type: backend
|
||||
label: Device API
|
||||
sublabel: REST
|
||||
description: >
|
||||
Responsibility: serves telemetry and device data to the customer app.
|
||||
Interfaces: provides REST over HTTPS; requires TSDB and device registry.
|
||||
ops-dashboard:
|
||||
type: frontend
|
||||
label: Ops dashboard
|
||||
sublabel: Grafana
|
||||
brand: grafana
|
||||
description: >
|
||||
Responsibility: fleet view for the operations team.
|
||||
Interfaces: provides web UI over HTTPS; requires read-only SQL on TSDB.
|
||||
customer-app:
|
||||
type: frontend
|
||||
label: Customer app
|
||||
sublabel: mobile
|
||||
description: >
|
||||
Responsibility: shows the homeowner their heat pump and alerts.
|
||||
Interfaces: provides the mobile UI; requires Device API and push.
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
homeowner:
|
||||
type: external
|
||||
label: Homeowner
|
||||
sublabel: customer app
|
||||
description: >
|
||||
Owns a heat pump; checks temperatures and gets fault alerts.
|
||||
ops-engineer:
|
||||
type: external
|
||||
label: Ops engineer
|
||||
sublabel: operations team
|
||||
description: >
|
||||
Watches the fleet, handles alerts and plans service visits.
|
||||
installer:
|
||||
type: external
|
||||
label: Installer
|
||||
sublabel: field service
|
||||
description: >
|
||||
Mounts heat pumps on site and registers them with the platform.
|
||||
@@ -0,0 +1,27 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
telemetry-platform:
|
||||
type: cloud
|
||||
label: IoT Telemetry
|
||||
sublabel: the system
|
||||
description: >
|
||||
Everything we build and run; broker, ingest, stores, rules and apps.
|
||||
diagram: 0500-building-blocks/level-1.archify
|
||||
heat-pump:
|
||||
type: external
|
||||
label: Heat pump
|
||||
sublabel: field device
|
||||
description: >
|
||||
Sends telemetry every 30 seconds over MQTT; built by a partner.
|
||||
push-service:
|
||||
type: external
|
||||
label: Push service
|
||||
sublabel: mobile push
|
||||
description: >
|
||||
Delivers push notifications to the customer app.
|
||||
service-desk:
|
||||
type: external
|
||||
label: Service desk
|
||||
sublabel: ticketing
|
||||
description: >
|
||||
Opens service tickets for faults that need a visit.
|
||||
@@ -0,0 +1,34 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Technical context
|
||||
output: technical-context.html
|
||||
components:
|
||||
- ref: engineer
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: wiki-admin
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: wiki
|
||||
pos: [280, 160]
|
||||
size: [150, 60]
|
||||
- ref: idp
|
||||
pos: [520, 60]
|
||||
size: [150, 60]
|
||||
- ref: object-store
|
||||
pos: [520, 260]
|
||||
size: [150, 60]
|
||||
connections:
|
||||
- { from: engineer, to: wiki, label: HTTPS, variant: emphasis }
|
||||
- { from: wiki-admin, to: wiki, label: kubectl }
|
||||
- { from: wiki, to: idp, label: OIDC, variant: security }
|
||||
- { from: wiki, to: object-store, label: S3 API }
|
||||
cards:
|
||||
- dot: cyan
|
||||
title: 3 Domain interfaces
|
||||
items:
|
||||
- "Engineer: in edits, queries, files; out pages, results"
|
||||
- "Wiki admin: in deploys, restore commands; out status, logs"
|
||||
- "Identity provider: out sign-in code, groups; in token requests"
|
||||
- "Object storage: out attachments, backups; in signed URL reads"
|
||||
@@ -0,0 +1,50 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Building blocks - level 1
|
||||
output: level-1.html
|
||||
decisions: [postgres-full-text-search]
|
||||
components:
|
||||
- ref: engineer
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: web
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: api
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: db
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
- ref: backup-job
|
||||
pos: [670, 360]
|
||||
size: [130, 60]
|
||||
- ref: idp
|
||||
pos: [670, 60]
|
||||
size: [130, 60]
|
||||
- ref: object-store
|
||||
pos: [880, 160]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Team Wiki
|
||||
wraps: [web, api, db, backup-job]
|
||||
connections:
|
||||
- { from: engineer, to: web, label: HTTPS, variant: emphasis }
|
||||
- { from: web, to: api, label: JSON, variant: emphasis }
|
||||
- { from: api, to: db, label: SQL, labelAt: [525, 234], decisions: [postgres-full-text-search] }
|
||||
- { from: api, to: idp, label: OIDC, variant: security }
|
||||
- { from: api, to: object-store, label: files }
|
||||
- { from: backup-job, to: db, label: dump }
|
||||
- { from: backup-job, to: object-store, label: upload }
|
||||
cards:
|
||||
- dot: amber
|
||||
title: 8 Crosscutting concepts
|
||||
items:
|
||||
- "Security: OIDC session, every request checked against space groups"
|
||||
- "Persistence: PostgreSQL is the only source of truth"
|
||||
- "Errors: API returns problem JSON; the web app shows a retry"
|
||||
- "Logging: JSON to stdout, collected by the cluster"
|
||||
- "Backups: nightly dump to object storage, 30 days"
|
||||
- "Config: environment variables, secrets from the vault"
|
||||
@@ -0,0 +1,55 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Building blocks - level 2, Wiki API
|
||||
output: wiki-api.html
|
||||
components:
|
||||
- ref: web
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- id: auth
|
||||
type: security
|
||||
label: Auth
|
||||
sublabel: OIDC session
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- id: pages
|
||||
type: backend
|
||||
label: Pages
|
||||
sublabel: revisions
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- id: search
|
||||
type: backend
|
||||
label: Search
|
||||
sublabel: full-text
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
decisions: [postgres-full-text-search]
|
||||
- id: attachments
|
||||
type: backend
|
||||
label: Attachments
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- ref: idp
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: db
|
||||
pos: [460, 210]
|
||||
size: [130, 60]
|
||||
- ref: object-store
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Wiki API
|
||||
wraps: [auth, pages, search, attachments]
|
||||
connections:
|
||||
- { from: web, to: auth, label: sign in, variant: security }
|
||||
- { from: web, to: pages, label: edit, labelAt: [222, 182] }
|
||||
- { from: web, to: search, label: query }
|
||||
- { from: web, to: attachments, label: upload }
|
||||
- { from: auth, to: idp, label: OIDC, variant: security }
|
||||
- { from: pages, to: db, label: SQL }
|
||||
- { from: search, to: db, label: SQL }
|
||||
- { from: attachments, to: object-store, label: S3 }
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
diagram_type: sequence
|
||||
meta:
|
||||
title: Runtime - search and open a page
|
||||
output: search-page.html
|
||||
participants:
|
||||
- ref: engineer
|
||||
- ref: web
|
||||
- ref: api
|
||||
- { ref: db, label: DB }
|
||||
- { ref: object-store, label: Object store }
|
||||
messages:
|
||||
- { from: engineer, to: web, y: 160, label: type query, variant: emphasis }
|
||||
- { from: web, to: api, y: 210, label: GET /search }
|
||||
- { from: api, to: db, y: 260, label: full-text query }
|
||||
- { from: db, to: api, y: 310, label: ranked pages, variant: return }
|
||||
- { from: api, to: web, y: 360, label: results, variant: return }
|
||||
- { from: web, to: api, y: 410, label: GET /pages/id }
|
||||
- { from: api, to: object-store, y: 460, label: signed URL }
|
||||
- { from: api, to: web, y: 510, label: page, variant: return }
|
||||
@@ -0,0 +1,19 @@
|
||||
schema_version: 1
|
||||
diagram_type: sequence
|
||||
meta:
|
||||
title: Runtime - sign in
|
||||
output: sign-in.html
|
||||
decisions: [sso-only-via-company-idp]
|
||||
participants:
|
||||
- ref: engineer
|
||||
- ref: web
|
||||
- ref: api
|
||||
- { ref: idp, label: Company IdP }
|
||||
messages:
|
||||
- { from: engineer, to: web, y: 160, label: open wiki, variant: emphasis }
|
||||
- { from: web, to: api, y: 210, label: GET /session }
|
||||
- { from: api, to: web, y: 260, label: redirect, variant: return }
|
||||
- { from: web, to: idp, y: 310, label: OIDC login, variant: security }
|
||||
- { from: idp, to: api, y: 360, label: code, variant: return }
|
||||
- { from: api, to: idp, y: 410, label: token exchange, variant: security }
|
||||
- { from: api, to: web, y: 460, label: session cookie, variant: return }
|
||||
@@ -0,0 +1,60 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Deployment - production
|
||||
output: production.html
|
||||
components:
|
||||
- ref: engineer
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- id: ingress
|
||||
type: security
|
||||
label: Ingress
|
||||
sublabel: TLS
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: web
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: api
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: db
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
- ref: backup-job
|
||||
pos: [670, 360]
|
||||
size: [130, 60]
|
||||
- ref: idp
|
||||
pos: [460, 460]
|
||||
size: [130, 60]
|
||||
- ref: object-store
|
||||
pos: [880, 460]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Company cluster
|
||||
wraps: [ingress, web, api, db, backup-job]
|
||||
- kind: security-group
|
||||
label: App node pool
|
||||
wraps: [web, api]
|
||||
- kind: security-group
|
||||
label: Data node
|
||||
wraps: [db, backup-job]
|
||||
connections:
|
||||
- { from: engineer, to: ingress, label: HTTPS, variant: emphasis }
|
||||
- { from: ingress, to: web, label: static }
|
||||
- { from: ingress, to: api, label: /api, variant: emphasis }
|
||||
- { from: api, to: db, label: SQL }
|
||||
- { from: api, to: idp, label: OIDC, variant: security, labelAt: [525, 234] }
|
||||
- { from: backup-job, to: db, label: dump }
|
||||
- { from: backup-job, to: object-store, label: S3 }
|
||||
cards:
|
||||
- dot: slate
|
||||
title: 7 Mapping
|
||||
items:
|
||||
- "Web App -> app node pool, nginx pod, namespace team-wiki"
|
||||
- "Wiki API -> app node pool, 2 pods behind the ingress"
|
||||
- "Database -> data node, PostgreSQL stateful set with volume"
|
||||
- "Backup job -> data node, Kubernetes CronJob"
|
||||
- "Attachments, backups -> company object storage buckets"
|
||||
@@ -0,0 +1,77 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Quality tree
|
||||
output: quality-tree.html
|
||||
legend:
|
||||
mode: hidden
|
||||
components:
|
||||
- id: quality
|
||||
type: cloud
|
||||
label: Quality
|
||||
pos: [40, 200]
|
||||
size: [130, 120]
|
||||
- id: usability
|
||||
type: frontend
|
||||
label: Usability
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- id: security
|
||||
type: security
|
||||
label: Security
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- id: reliability
|
||||
type: backend
|
||||
label: Reliability
|
||||
pos: [250, 310]
|
||||
size: [130, 60]
|
||||
- id: maintainability
|
||||
type: backend
|
||||
label: Maintainability
|
||||
pos: [250, 460]
|
||||
size: [130, 60]
|
||||
- id: q-search
|
||||
type: external
|
||||
label: Search under 1 s
|
||||
pos: [460, 60]
|
||||
size: [170, 60]
|
||||
- id: q-leaver
|
||||
type: external
|
||||
label: Leaver out in 5 min
|
||||
pos: [460, 160]
|
||||
size: [170, 60]
|
||||
- id: q-restore
|
||||
type: external
|
||||
label: Restore in 30 min
|
||||
pos: [460, 260]
|
||||
size: [170, 60]
|
||||
- id: q-pod
|
||||
type: external
|
||||
label: Pod back in 1 min
|
||||
pos: [460, 360]
|
||||
size: [170, 60]
|
||||
- id: q-upgrade
|
||||
type: external
|
||||
label: Upgrade in 1 hour
|
||||
pos: [460, 460]
|
||||
size: [170, 60]
|
||||
connections:
|
||||
- { from: quality, to: usability }
|
||||
- { from: quality, to: security }
|
||||
- { from: quality, to: reliability }
|
||||
- { from: quality, to: maintainability }
|
||||
- { from: usability, to: q-search }
|
||||
- { from: security, to: q-leaver }
|
||||
- { from: reliability, to: q-restore }
|
||||
- { from: reliability, to: q-pod }
|
||||
- { from: maintainability, to: q-upgrade }
|
||||
cards:
|
||||
- dot: emerald
|
||||
title: 10 Quality scenarios
|
||||
items:
|
||||
- "Search: an engineer searches 5000 pages; results in under 1 s"
|
||||
- "Leaver: HR disables an account; wiki access ends within 5 minutes"
|
||||
- "Restore: the database is lost; the admin restores last night in 30 minutes"
|
||||
- "Crash: an API pod dies; the cluster restarts it, no data lost, back in 1 minute"
|
||||
- "Upgrade: a new wiki version ships; one admin rolls it out in 1 hour"
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: postgres-full-text-search
|
||||
title: PostgreSQL full-text search, no search engine
|
||||
status: accepted
|
||||
date: 2026-10-05
|
||||
elements: [api, db]
|
||||
diagrams: [0500-building-blocks/level-1.archify, 0500-building-blocks/wiki-api.archify]
|
||||
context: >
|
||||
The wiki holds a few thousand pages for one team. A
|
||||
separate search engine would be one more service to run,
|
||||
back up and keep in sync.
|
||||
decision: >
|
||||
Search uses PostgreSQL full-text search on a generated
|
||||
column of each page. The API queries it in the same
|
||||
transaction as the page data.
|
||||
consequences: >
|
||||
Results are never stale and backups cover search too.
|
||||
Ranking is simpler than a search engine. Revisit when
|
||||
the wiki grows past 100000 pages.
|
||||
@@ -0,0 +1,19 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: sso-only-via-company-idp
|
||||
title: Sign-in only through the company identity provider
|
||||
status: accepted
|
||||
date: 2026-10-12
|
||||
elements: [idp, api, web]
|
||||
diagrams: [0300-context/technical-context.archify, 0600-runtime/sign-in.archify]
|
||||
context: >
|
||||
Leavers kept access to the old wiki because it had its
|
||||
own passwords. Nobody owned the user list.
|
||||
decision: >
|
||||
The wiki has no local accounts. Users sign in with OIDC
|
||||
at the company identity provider; team groups map to
|
||||
wiki spaces.
|
||||
consequences: >
|
||||
Offboarding removes wiki access at once. The wiki cannot
|
||||
be used when the identity provider is down. The admin
|
||||
needs a break-glass account kept in the vault.
|
||||
@@ -0,0 +1,85 @@
|
||||
schema_version: 1
|
||||
model:
|
||||
id: team-wiki
|
||||
name: Team Wiki
|
||||
description: >
|
||||
An arc42 example: a small self-hosted wiki for an engineering team.
|
||||
1 Requirements, quality goals, stakeholders; 2 Constraints;
|
||||
4 Solution strategy; 11 Risks; 12 Glossary: cards on this overview.
|
||||
3 Context: this overview (business) and 0300-context (technical).
|
||||
5 Building blocks: 0500-building-blocks, black boxes in registry/.
|
||||
6 Runtime: 0600-runtime. 7 Deployment: 0700-deployment.
|
||||
8 Crosscutting concepts: card on 0500-building-blocks/level-1.
|
||||
9 Decisions: decisions/. 10 Quality: 1000-quality.
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Team Wiki - business context
|
||||
output: main.html
|
||||
decisions: [sso-only-via-company-idp]
|
||||
components:
|
||||
- ref: engineer
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: wiki-admin
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: wiki
|
||||
pos: [280, 160]
|
||||
size: [150, 60]
|
||||
- ref: idp
|
||||
pos: [520, 60]
|
||||
size: [150, 60]
|
||||
- ref: object-store
|
||||
pos: [520, 260]
|
||||
size: [150, 60]
|
||||
connections:
|
||||
- { from: engineer, to: wiki, label: reads, variant: emphasis }
|
||||
- { from: wiki-admin, to: wiki, label: runs }
|
||||
- { from: wiki, to: idp, label: sign-in, variant: security }
|
||||
- { from: wiki, to: object-store, label: files }
|
||||
cards:
|
||||
- dot: orange
|
||||
title: 1.1 Requirements
|
||||
items:
|
||||
- "Create, edit and version pages in team spaces"
|
||||
- "Full-text search over every page a user may read"
|
||||
- "Attach files to pages"
|
||||
- "Admin restores the wiki from a nightly backup"
|
||||
- dot: cyan
|
||||
title: 1.2 Quality goals
|
||||
items:
|
||||
- "Findability: any page found in two searches"
|
||||
- "Security: only current staff can read"
|
||||
- "Operability: one person runs it in an hour a week"
|
||||
- dot: amber
|
||||
title: 1.3 Stakeholders
|
||||
items:
|
||||
- "Engineer: fast search, simple editor"
|
||||
- "Wiki admin: little routine work, easy restore"
|
||||
- "Security officer: no access after leaving"
|
||||
- "Team lead: one place for team knowledge"
|
||||
- dot: slate
|
||||
title: 2 Constraints
|
||||
items:
|
||||
- "Runs on the company Kubernetes cluster"
|
||||
- "Sign-in only via the company identity provider"
|
||||
- dot: emerald
|
||||
title: 4 Solution strategy
|
||||
items:
|
||||
- "React, Node.js API, PostgreSQL: one stack the team knows"
|
||||
- "Three blocks plus a backup job: operability"
|
||||
- "Findability: PostgreSQL full-text search (postgres-full-text-search)"
|
||||
- "Security: OIDC at the company IdP only (sso-only-via-company-idp)"
|
||||
- "Operability: files and backups in company object storage"
|
||||
- dot: rose
|
||||
title: 11 Risks and technical debt
|
||||
items:
|
||||
- "Risk: identity provider outage blocks all sign-ins"
|
||||
- "Risk: backups untested until a restore drill is run"
|
||||
- "Debt: attachments are not virus-scanned yet"
|
||||
- dot: violet
|
||||
title: 12 Glossary
|
||||
items:
|
||||
- "Space: pages of one team, mapped to a group"
|
||||
- "Revision: one saved version of a page"
|
||||
- "Attachment: a file stored beside a page"
|
||||
@@ -0,0 +1,37 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
web:
|
||||
type: frontend
|
||||
label: Web App
|
||||
sublabel: React
|
||||
brand: react
|
||||
description: >
|
||||
Responsibility: page editor, viewer and search box in the
|
||||
browser. Interfaces: provides the UI over HTTPS; requires
|
||||
the Wiki API (JSON).
|
||||
api:
|
||||
type: backend
|
||||
label: Wiki API
|
||||
sublabel: REST
|
||||
description: >
|
||||
Responsibility: pages, search, attachments and access checks;
|
||||
the only writer of the database. Interfaces: provides REST
|
||||
JSON; requires the IdP (OIDC), the database (SQL), object storage (S3).
|
||||
diagram: 0500-building-blocks/wiki-api.archify
|
||||
db:
|
||||
type: database
|
||||
label: Database
|
||||
sublabel: PostgreSQL
|
||||
brand: postgresql
|
||||
description: >
|
||||
Responsibility: pages, revisions, users and the full-text
|
||||
index; the source of truth. Interfaces: provides SQL to the
|
||||
API and dumps to the backup job.
|
||||
backup-job:
|
||||
type: backend
|
||||
label: Backup job
|
||||
sublabel: nightly
|
||||
description: >
|
||||
Responsibility: dumps the database at 02:00, keeps 30 days.
|
||||
Interfaces: requires the database (pg_dump) and the backup
|
||||
bucket (S3); provides none.
|
||||
@@ -0,0 +1,15 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
engineer:
|
||||
type: external
|
||||
label: Engineer
|
||||
sublabel: team member
|
||||
description: >
|
||||
Reads and edits pages, searches, attaches files; signs in
|
||||
with the company account.
|
||||
wiki-admin:
|
||||
type: external
|
||||
label: Wiki admin
|
||||
sublabel: platform team
|
||||
description: >
|
||||
Runs the wiki, restores backups and manages spaces.
|
||||
@@ -0,0 +1,24 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
wiki:
|
||||
type: cloud
|
||||
label: Team Wiki
|
||||
sublabel: the system
|
||||
description: >
|
||||
Everything we build and run; web app, API, database and
|
||||
the backup job.
|
||||
diagram: 0500-building-blocks/level-1.archify
|
||||
idp:
|
||||
type: security
|
||||
label: Identity provider
|
||||
sublabel: company SSO
|
||||
description: >
|
||||
The company identity provider; signs users in over OIDC
|
||||
and owns team membership.
|
||||
object-store:
|
||||
type: external
|
||||
label: Object storage
|
||||
sublabel: S3-compatible
|
||||
description: >
|
||||
Company object storage; holds attachments and nightly
|
||||
backups in separate buckets.
|
||||
@@ -0,0 +1,36 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Business actors and services
|
||||
output: business-services.html
|
||||
components:
|
||||
- ref: hr
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: manager
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: service-desk
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: onboard
|
||||
pos: [250, 160]
|
||||
size: [150, 60]
|
||||
diagram: 0100-business/onboard-employee.archify
|
||||
- ref: onboarding-svc
|
||||
pos: [480, 60]
|
||||
size: [130, 60]
|
||||
- ref: workplace-svc
|
||||
pos: [480, 260]
|
||||
size: [130, 60]
|
||||
- ref: new-hire
|
||||
pos: [690, 160]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: hr, to: onboard, label: assigned }
|
||||
- { from: manager, to: onboard, label: assigned }
|
||||
- { from: service-desk, to: onboard, label: assigned }
|
||||
- { from: onboard, to: onboarding-svc, label: realizes }
|
||||
- { from: onboard, to: workplace-svc, label: realizes }
|
||||
- { from: onboarding-svc, to: new-hire, label: serves, variant: emphasis }
|
||||
- { from: workplace-svc, to: new-hire, label: serves }
|
||||
@@ -0,0 +1,25 @@
|
||||
schema_version: 2
|
||||
diagram_type: workflow
|
||||
meta:
|
||||
title: Onboard Employee
|
||||
output: onboard-employee.html
|
||||
viewBox: [1040, 660]
|
||||
lanes:
|
||||
- { id: hr-lane, label: HR }
|
||||
- { id: manager-lane, label: Hiring manager }
|
||||
- { id: desk-lane, label: IT service desk }
|
||||
- { id: hire-lane, label: New hire }
|
||||
mainPath: [contract-signed, record, request, create-accounts, issue-laptop, first-day]
|
||||
nodes:
|
||||
- { ref: contract-signed, lane: hr-lane, col: 0, width: 120 }
|
||||
- { id: record, lane: hr-lane, col: 1, type: backend, label: Create record, tag: Business Process }
|
||||
- { id: request, lane: manager-lane, col: 2, type: frontend, label: Request access, tag: Business Process }
|
||||
- { ref: create-accounts, lane: desk-lane, col: 3, width: 120, decisions: [hr-system-drives-identity] }
|
||||
- { ref: issue-laptop, lane: desk-lane, col: 4, decisions: [zero-touch-laptops] }
|
||||
- { ref: first-day, lane: hire-lane, col: 5 }
|
||||
edges:
|
||||
- { from: contract-signed, to: record, label: triggers }
|
||||
- { from: record, to: request, label: notifies }
|
||||
- { from: request, to: create-accounts, label: ticket }
|
||||
- { from: create-accounts, to: issue-laptop, label: then }
|
||||
- { from: issue-laptop, to: first-day, label: hand over }
|
||||
@@ -0,0 +1,44 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Application components, services and data
|
||||
output: application-components.html
|
||||
components:
|
||||
- ref: provisioning-svc
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- ref: portal
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: device-svc
|
||||
pos: [670, 60]
|
||||
size: [130, 60]
|
||||
- ref: hr-system
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: iam
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: itsm
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: mdm-app
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
- ref: employee-record
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: account
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: asset
|
||||
pos: [460, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: iam, to: provisioning-svc, label: realizes }
|
||||
- { from: itsm, to: portal, label: realizes }
|
||||
- { from: mdm-app, to: device-svc, label: realizes }
|
||||
- { from: hr-system, to: employee-record, label: writes, labelAt: [105, 234] }
|
||||
- { from: iam, to: account, label: writes, labelAt: [315, 234] }
|
||||
- { from: itsm, to: asset, label: writes, labelAt: [525, 234] }
|
||||
- { from: mdm-app, to: asset, label: reads }
|
||||
@@ -0,0 +1,29 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Application cooperation
|
||||
output: application-cooperation.html
|
||||
decisions: [hr-system-drives-identity]
|
||||
components:
|
||||
- ref: hr-system
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: iam
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
diagram: 0200-application/application-components.archify
|
||||
- ref: itsm
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: collab
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: mdm-app
|
||||
pos: [460, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: hr-system, to: iam, label: joiner event, variant: emphasis, decisions: [hr-system-drives-identity] }
|
||||
- { from: hr-system, to: itsm, label: laptop request }
|
||||
- { from: iam, to: collab, label: provisions }
|
||||
- { from: iam, to: mdm-app, label: users }
|
||||
- { from: itsm, to: mdm-app, label: assigns }
|
||||
@@ -0,0 +1,42 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Technology layer
|
||||
output: infrastructure.html
|
||||
decisions: [zero-touch-laptops]
|
||||
components:
|
||||
- ref: hr-saas
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: sso
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- ref: directory
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: itsm-saas
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: mdm-saas
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: laptop
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
- ref: office-net
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: SaaS tenants
|
||||
wraps: [hr-saas, sso, directory, itsm-saas, mdm-saas]
|
||||
- kind: security-group
|
||||
label: Office
|
||||
wraps: [laptop, office-net]
|
||||
connections:
|
||||
- { from: directory, to: sso, label: realizes }
|
||||
- { from: sso, to: hr-saas, label: serves }
|
||||
- { from: sso, to: itsm-saas, label: serves }
|
||||
- { from: directory, to: mdm-saas, label: devices, labelAt: [525, 134] }
|
||||
- { from: mdm-saas, to: laptop, label: manages, labelAt: [525, 234], variant: security, decisions: [zero-touch-laptops] }
|
||||
- { from: laptop, to: office-net, label: connects }
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: hr-system-drives-identity
|
||||
title: The HR system drives identity
|
||||
status: accepted
|
||||
date: 2026-09-08
|
||||
elements: [hr-system, iam, account, employee-record]
|
||||
diagrams: [0200-application/application-cooperation.archify, 0100-business/onboard-employee.archify]
|
||||
context: >
|
||||
Accounts were created by hand from emails sent by HR.
|
||||
New hires waited days for access, and leavers kept
|
||||
accounts for weeks after their last day.
|
||||
decision: >
|
||||
The HR system is the source of truth for joiners and
|
||||
leavers. Identity management reads its events and
|
||||
creates, changes and disables accounts automatically.
|
||||
consequences: >
|
||||
No account without an employee record. HR data quality
|
||||
now decides access, so start dates must be right. The
|
||||
service desk only handles exceptions.
|
||||
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: zero-touch-laptops
|
||||
title: Zero-touch enrollment for laptops
|
||||
status: accepted
|
||||
date: 2026-10-05
|
||||
elements: [mdm-app, mdm-saas, laptop, device-svc, asset]
|
||||
diagrams: [0300-technology/infrastructure.archify, 0100-business/onboard-employee.archify]
|
||||
context: >
|
||||
Every laptop was imaged by hand at the service desk,
|
||||
which took half a day per device and left settings that
|
||||
differed from one laptop to the next.
|
||||
decision: >
|
||||
Laptops are registered in device management at purchase.
|
||||
They configure themselves at first sign-in over any
|
||||
internet connection; the office network needs no VPN.
|
||||
consequences: >
|
||||
Laptops can be shipped straight to the new hire. A lost
|
||||
laptop is wiped remotely. Setup depends on the MDM
|
||||
tenant being reachable on the first day.
|
||||
@@ -0,0 +1,59 @@
|
||||
schema_version: 1
|
||||
model:
|
||||
id: hr-onboarding
|
||||
name: HR Onboarding
|
||||
description: >
|
||||
An ArchiMate example: how a new employee is onboarded, from signed
|
||||
contract to first day. Business, application and technology layers
|
||||
map to folders; each element's ArchiMate type is shown as its tag.
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: HR Onboarding - layered view
|
||||
output: main.html
|
||||
components:
|
||||
- ref: new-hire
|
||||
pos: [40, 70]
|
||||
size: [170, 60]
|
||||
- ref: onboarding-svc
|
||||
pos: [270, 70]
|
||||
size: [170, 60]
|
||||
- ref: onboard
|
||||
pos: [500, 70]
|
||||
size: [170, 60]
|
||||
- ref: portal
|
||||
pos: [40, 270]
|
||||
size: [170, 60]
|
||||
- ref: iam
|
||||
pos: [270, 270]
|
||||
size: [170, 60]
|
||||
- ref: provisioning-svc
|
||||
pos: [500, 270]
|
||||
size: [170, 60]
|
||||
- ref: office-net
|
||||
pos: [40, 470]
|
||||
size: [170, 60]
|
||||
- ref: sso
|
||||
pos: [270, 470]
|
||||
size: [170, 60]
|
||||
- ref: directory
|
||||
pos: [500, 470]
|
||||
size: [170, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Business layer
|
||||
wraps: [new-hire, onboarding-svc, onboard]
|
||||
- kind: region
|
||||
label: Application layer
|
||||
wraps: [portal, iam, provisioning-svc]
|
||||
- kind: region
|
||||
label: Technology layer
|
||||
wraps: [office-net, sso, directory]
|
||||
connections:
|
||||
- { from: onboarding-svc, to: new-hire, label: serves, variant: emphasis }
|
||||
- { from: onboard, to: onboarding-svc, label: realizes }
|
||||
- { from: portal, to: new-hire, label: serves, labelAt: [125, 197] }
|
||||
- { from: provisioning-svc, to: onboard, label: serves, labelAt: [585, 197] }
|
||||
- { from: iam, to: provisioning-svc, label: realizes }
|
||||
- { from: office-net, to: portal, label: serves, labelAt: [125, 397] }
|
||||
- { from: sso, to: iam, label: serves, labelAt: [355, 397] }
|
||||
- { from: directory, to: sso, label: realizes }
|
||||
@@ -0,0 +1,81 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
hr-system:
|
||||
type: backend
|
||||
label: HR system
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Holds employee records and contracts; the source of truth for joiners.
|
||||
diagram: 0200-application/application-cooperation.archify
|
||||
iam:
|
||||
type: security
|
||||
label: Identity mgmt
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Creates and removes accounts from HR events; single sign-on for all apps.
|
||||
diagram: 0200-application/application-components.archify
|
||||
itsm:
|
||||
type: backend
|
||||
label: IT service mgmt
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Tickets, requests and the asset inventory of the IT service desk.
|
||||
mdm-app:
|
||||
type: backend
|
||||
label: Device mgmt
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Enrolls laptops, pushes policies and apps, wipes lost devices.
|
||||
collab:
|
||||
type: backend
|
||||
label: Mail and chat
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Mailbox, calendar and chat; accounts provisioned by identity management.
|
||||
provisioning-svc:
|
||||
type: cloud
|
||||
label: Provisioning
|
||||
sublabel: App Service
|
||||
tag: App Service
|
||||
description: >
|
||||
Account create, change and disable, triggered by the HR system.
|
||||
device-svc:
|
||||
type: cloud
|
||||
label: Enrollment
|
||||
sublabel: App Service
|
||||
tag: App Service
|
||||
description: >
|
||||
Zero-touch enrollment; the laptop configures itself at first sign-in.
|
||||
portal:
|
||||
type: frontend
|
||||
label: Service portal
|
||||
sublabel: App Interface
|
||||
tag: App Interface
|
||||
description: >
|
||||
Where managers request equipment and access for a new hire.
|
||||
employee-record:
|
||||
type: database
|
||||
label: Employee record
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
Name, role, department, manager and start date of an employee.
|
||||
account:
|
||||
type: database
|
||||
label: Account
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
User id, groups and licences of one person.
|
||||
asset:
|
||||
type: database
|
||||
label: Asset
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
A laptop with serial number, owner and enrollment state.
|
||||
@@ -0,0 +1,81 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
new-hire:
|
||||
type: external
|
||||
label: New hire
|
||||
sublabel: Business Actor
|
||||
tag: Business Actor
|
||||
description: >
|
||||
The person joining the company; signs the contract and starts on day one.
|
||||
hr:
|
||||
type: external
|
||||
label: HR officer
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
Prepares the contract and the employee record; owns the onboarding process.
|
||||
manager:
|
||||
type: external
|
||||
label: Hiring manager
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
Requests access and equipment for the new hire and plans the first day.
|
||||
service-desk:
|
||||
type: external
|
||||
label: IT service desk
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
Creates accounts, prepares the laptop and hands it over.
|
||||
onboard:
|
||||
type: backend
|
||||
label: Onboard Employee
|
||||
sublabel: Business Process
|
||||
tag: Business Process
|
||||
description: >
|
||||
From signed contract to a productive first day.
|
||||
diagram: 0100-business/onboard-employee.archify
|
||||
onboarding-svc:
|
||||
type: cloud
|
||||
label: Onboarding
|
||||
sublabel: Business Service
|
||||
tag: Business Service
|
||||
description: >
|
||||
What the new hire gets from HR; contract, record, welcome and first day plan.
|
||||
diagram: 0100-business/business-services.archify
|
||||
workplace-svc:
|
||||
type: cloud
|
||||
label: Workplace setup
|
||||
sublabel: Business Service
|
||||
tag: Business Service
|
||||
description: >
|
||||
Accounts and a ready laptop, delivered by the IT service desk.
|
||||
contract-signed:
|
||||
type: messagebus
|
||||
label: Contract signed
|
||||
sublabel: Business Event
|
||||
tag: Business Event
|
||||
description: >
|
||||
Starts the process; HR records the start date.
|
||||
create-accounts:
|
||||
type: backend
|
||||
label: Create accounts
|
||||
sublabel: Business Process
|
||||
tag: Business Process
|
||||
description: >
|
||||
Service desk confirms the accounts the identity system provisioned.
|
||||
issue-laptop:
|
||||
type: backend
|
||||
label: Issue laptop
|
||||
sublabel: Business Process
|
||||
tag: Business Process
|
||||
description: >
|
||||
An enrolled laptop is assigned and handed over.
|
||||
first-day:
|
||||
type: messagebus
|
||||
label: First day
|
||||
sublabel: Business Event
|
||||
tag: Business Event
|
||||
description: >
|
||||
The new hire signs in for the first time; the process ends.
|
||||
@@ -0,0 +1,53 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
hr-saas:
|
||||
type: cloud
|
||||
label: HR tenant
|
||||
sublabel: Node
|
||||
tag: Node
|
||||
description: >
|
||||
The SaaS tenant that runs the HR system.
|
||||
diagram: 0300-technology/infrastructure.archify
|
||||
itsm-saas:
|
||||
type: cloud
|
||||
label: ITSM tenant
|
||||
sublabel: Node
|
||||
tag: Node
|
||||
description: >
|
||||
The SaaS tenant that runs IT service management.
|
||||
mdm-saas:
|
||||
type: cloud
|
||||
label: MDM tenant
|
||||
sublabel: Node
|
||||
tag: Node
|
||||
description: >
|
||||
The SaaS tenant that runs device management.
|
||||
directory:
|
||||
type: backend
|
||||
label: Directory
|
||||
sublabel: System Software
|
||||
tag: System Software
|
||||
description: >
|
||||
Cloud directory service; users, groups and devices.
|
||||
diagram: 0300-technology/infrastructure.archify
|
||||
sso:
|
||||
type: security
|
||||
label: Single sign-on
|
||||
sublabel: Tech Service
|
||||
tag: Tech Service
|
||||
description: >
|
||||
SAML and OIDC sign-in for every SaaS tenant, backed by the directory.
|
||||
laptop:
|
||||
type: frontend
|
||||
label: Laptop
|
||||
sublabel: Device
|
||||
tag: Device
|
||||
description: >
|
||||
Company laptop, enrolled before it is handed over.
|
||||
office-net:
|
||||
type: messagebus
|
||||
label: Office network
|
||||
sublabel: Network
|
||||
tag: Network
|
||||
description: >
|
||||
Wired and wireless network of the office; internet only, no VPN.
|
||||
@@ -0,0 +1,31 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Business layer
|
||||
output: business-layer.html
|
||||
components:
|
||||
- ref: customer
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: insurant
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: claim-registration
|
||||
pos: [460, 60]
|
||||
size: [150, 60]
|
||||
- ref: claim-payment
|
||||
pos: [460, 260]
|
||||
size: [150, 60]
|
||||
- ref: handle-claim
|
||||
pos: [690, 160]
|
||||
size: [150, 60]
|
||||
- ref: claims-handler
|
||||
pos: [920, 160]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: customer, to: insurant, label: assigned to }
|
||||
- { from: claim-registration, to: insurant, label: serves, variant: emphasis }
|
||||
- { from: claim-payment, to: insurant, label: serves }
|
||||
- { from: handle-claim, to: claim-registration, label: realizes }
|
||||
- { from: handle-claim, to: claim-payment, label: realizes }
|
||||
- { from: claims-handler, to: handle-claim, label: assigned to }
|
||||
@@ -0,0 +1,29 @@
|
||||
schema_version: 2
|
||||
diagram_type: workflow
|
||||
meta:
|
||||
title: Handle Claim
|
||||
output: handle-claim.html
|
||||
viewBox: [840, 534]
|
||||
lanes:
|
||||
- { id: insurant-lane, label: Insurant }
|
||||
- { id: handler-lane, label: Claims handler }
|
||||
- { id: systems-lane, label: Applications }
|
||||
mainPath: [damage, register, accept, valuate, pay]
|
||||
nodes:
|
||||
- { id: damage, lane: insurant-lane, col: 0, type: external, label: Damage occurs, tag: Business Event }
|
||||
- { ref: insurant, lane: insurant-lane, col: 5 }
|
||||
- { id: register, lane: handler-lane, col: 1, type: backend, label: Register, tag: Business Process }
|
||||
- { id: accept, lane: handler-lane, col: 2, type: security, label: Accept, sublabel: check coverage, tag: Business Process }
|
||||
- { id: valuate, lane: handler-lane, col: 3, type: backend, label: Valuate, tag: Business Process }
|
||||
- { id: pay, lane: handler-lane, col: 4, type: backend, label: Pay, tag: Business Process }
|
||||
- { ref: claims-app, lane: systems-lane, col: 2 }
|
||||
- { ref: payment, lane: systems-lane, col: 4 }
|
||||
edges:
|
||||
- { from: damage, to: register, label: report }
|
||||
- { from: register, to: accept }
|
||||
- { from: accept, to: valuate, label: covered }
|
||||
- { from: valuate, to: pay, label: approved }
|
||||
- { from: pay, to: insurant, label: paid }
|
||||
- { from: accept, to: insurant, label: rejected, role: branch, variant: dashed }
|
||||
- { from: claims-app, to: accept, label: serves, role: async }
|
||||
- { from: payment, to: pay, label: serves, role: async }
|
||||
@@ -0,0 +1,32 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Application cooperation
|
||||
output: application-cooperation.html
|
||||
components:
|
||||
- ref: portal
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: claims-app
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: crm
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- ref: policy-admin
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
decisions: [policies-stay-on-mainframe]
|
||||
- ref: doc-mgmt
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
decisions: [documents-in-object-storage]
|
||||
- ref: payment
|
||||
pos: [250, 460]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: portal, to: claims-app, label: claims, variant: emphasis }
|
||||
- { from: crm, to: claims-app, label: customer, labelAt: [315, 134] }
|
||||
- { from: policy-admin, to: claims-app, label: coverage, decisions: [policies-stay-on-mainframe] }
|
||||
- { from: claims-app, to: doc-mgmt, label: documents }
|
||||
- { from: claims-app, to: payment, label: payments, labelAt: [315, 334] }
|
||||
@@ -0,0 +1,36 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Application structure
|
||||
output: application-structure.html
|
||||
components:
|
||||
- ref: portal
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: claims-admin
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: claims-app
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: claim
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
- ref: crm
|
||||
pos: [670, 60]
|
||||
size: [130, 60]
|
||||
- ref: customer-file
|
||||
pos: [880, 60]
|
||||
size: [130, 60]
|
||||
- ref: policy-admin
|
||||
pos: [670, 260]
|
||||
size: [130, 60]
|
||||
- ref: policy
|
||||
pos: [880, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: claims-admin, to: portal, label: serves, variant: emphasis }
|
||||
- { from: claims-app, to: claims-admin, label: realizes }
|
||||
- { from: claims-app, to: claim, label: accesses, labelAt: [525, 234] }
|
||||
- { from: crm, to: customer-file, label: accesses }
|
||||
- { from: policy-admin, to: policy, label: accesses }
|
||||
@@ -0,0 +1,45 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Technology layer
|
||||
output: infrastructure.html
|
||||
components:
|
||||
- ref: dmz
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: doc-store
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
decisions: [documents-in-object-storage]
|
||||
- ref: app-servers
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: db-service
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: db-cluster
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- ref: mq
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: mainframe
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
decisions: [policies-stay-on-mainframe]
|
||||
- ref: dc-lan
|
||||
pos: [670, 360]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Data centre
|
||||
wraps: [doc-store, app-servers, db-service, db-cluster, mq, mainframe, dc-lan]
|
||||
connections:
|
||||
- { from: dmz, to: app-servers, label: HTTPS, variant: security }
|
||||
- { from: app-servers, to: doc-store, label: S3 API }
|
||||
- { from: db-service, to: app-servers, label: serves }
|
||||
- { from: db-cluster, to: db-service, label: realizes }
|
||||
- { from: app-servers, to: mq, label: MQ }
|
||||
- { from: mq, to: mainframe, label: bridge }
|
||||
- { from: db-cluster, to: dc-lan, label: LAN }
|
||||
- { from: mainframe, to: dc-lan, label: LAN, labelAt: [735, 234] }
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: policies-stay-on-mainframe
|
||||
title: Policies stay on the mainframe
|
||||
status: accepted
|
||||
date: 2026-09-08
|
||||
elements: [policy-admin, mainframe, mq, claims-app]
|
||||
diagrams: [0200-application/application-cooperation.archify, 0300-technology/infrastructure.archify]
|
||||
context: >
|
||||
The new claims application needs coverage data for every
|
||||
claim. Policy administration has run on the mainframe for
|
||||
twenty years and a migration was estimated at three years.
|
||||
decision: >
|
||||
Policy administration stays on the mainframe. The claims
|
||||
application reads coverage through the message queue bridge
|
||||
and never copies policies into its own database.
|
||||
consequences: >
|
||||
Accepting a claim depends on the mainframe being up during
|
||||
office hours. The queue bridge must be kept and tested.
|
||||
A later migration needs its own decision.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: documents-in-object-storage
|
||||
title: Claim documents in object storage
|
||||
status: accepted
|
||||
date: 2026-10-14
|
||||
elements: [doc-mgmt, doc-store, claims-app]
|
||||
diagrams: [0200-application/application-cooperation.archify, 0300-technology/infrastructure.archify]
|
||||
context: >
|
||||
Photos and expert reports were stored as blobs in the claims
|
||||
database. The database grew by forty gigabytes a month and
|
||||
backups no longer fitted the nightly window.
|
||||
decision: >
|
||||
Document management stores every file in the document store
|
||||
and keeps only a reference in the claim. The claims
|
||||
application never writes files itself.
|
||||
consequences: >
|
||||
Database backups shrink and stay fast. Documents get their own
|
||||
retention rules. A claim and its files are restored from two
|
||||
places after an incident.
|
||||
@@ -0,0 +1,59 @@
|
||||
schema_version: 1
|
||||
model:
|
||||
id: insurance-claims
|
||||
name: Insurance Claims
|
||||
description: >
|
||||
ArchiMate example: how Halbrook Mutual handles claims. The business,
|
||||
application and technology layers are mapped to folders, and each
|
||||
element's ArchiMate type is shown as its tag.
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Insurance Claims - layered view
|
||||
output: main.html
|
||||
components:
|
||||
- ref: insurant
|
||||
pos: [40, 70]
|
||||
size: [170, 60]
|
||||
- ref: claim-registration
|
||||
pos: [270, 70]
|
||||
size: [170, 60]
|
||||
- ref: handle-claim
|
||||
pos: [500, 70]
|
||||
size: [170, 60]
|
||||
- ref: portal
|
||||
pos: [40, 270]
|
||||
size: [170, 60]
|
||||
- ref: claims-app
|
||||
pos: [270, 270]
|
||||
size: [170, 60]
|
||||
- ref: claims-admin
|
||||
pos: [500, 270]
|
||||
size: [170, 60]
|
||||
- ref: dmz
|
||||
pos: [40, 470]
|
||||
size: [170, 60]
|
||||
- ref: app-servers
|
||||
pos: [270, 470]
|
||||
size: [170, 60]
|
||||
- ref: db-cluster
|
||||
pos: [500, 470]
|
||||
size: [170, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Business layer
|
||||
wraps: [insurant, claim-registration, handle-claim]
|
||||
- kind: region
|
||||
label: Application layer
|
||||
wraps: [portal, claims-app, claims-admin]
|
||||
- kind: region
|
||||
label: Technology layer
|
||||
wraps: [dmz, app-servers, db-cluster]
|
||||
connections:
|
||||
- { from: claim-registration, to: insurant, label: serves, variant: emphasis }
|
||||
- { from: handle-claim, to: claim-registration, label: realizes }
|
||||
- { from: portal, to: insurant, label: serves, labelAt: [125, 197] }
|
||||
- { from: claims-admin, to: handle-claim, label: serves, labelAt: [585, 197] }
|
||||
- { from: claims-app, to: claims-admin, label: realizes }
|
||||
- { from: dmz, to: portal, label: serves, labelAt: [125, 397] }
|
||||
- { from: app-servers, to: claims-app, label: serves, labelAt: [355, 397] }
|
||||
- { from: db-cluster, to: app-servers, label: serves }
|
||||
@@ -0,0 +1,76 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
portal:
|
||||
type: frontend
|
||||
label: Customer Portal
|
||||
sublabel: App Interface
|
||||
tag: App Interface
|
||||
description: >
|
||||
Web and mobile front end where customers report claims and
|
||||
follow their status.
|
||||
claims-admin:
|
||||
type: cloud
|
||||
label: Claims Admin
|
||||
sublabel: App Service
|
||||
tag: App Service
|
||||
description: >
|
||||
Register, look up and update claims; used by the portal and
|
||||
by claims handlers.
|
||||
claims-app:
|
||||
type: backend
|
||||
label: Claims App
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Owns claims from registration to payment; the system of
|
||||
record for claims.
|
||||
diagram: 0200-application/application-structure.archify
|
||||
crm:
|
||||
type: backend
|
||||
label: CRM
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Customer contacts, addresses and the customer file.
|
||||
policy-admin:
|
||||
type: backend
|
||||
label: Policy Admin
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Policies, coverage and premiums; runs on the mainframe.
|
||||
doc-mgmt:
|
||||
type: backend
|
||||
label: Document Mgmt
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Scanned letters, photos and expert reports attached to claims.
|
||||
payment:
|
||||
type: backend
|
||||
label: Payment
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Sends approved claim payments to the bank in daily batches.
|
||||
claim:
|
||||
type: database
|
||||
label: Claim
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
One reported damage, its valuation and its payment status.
|
||||
policy:
|
||||
type: database
|
||||
label: Policy
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
Coverage, deductible and term of an insurance contract.
|
||||
customer-file:
|
||||
type: database
|
||||
label: Customer File
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
Everything known about a customer; contacts, policies, claims.
|
||||
@@ -0,0 +1,50 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
customer:
|
||||
type: external
|
||||
label: Customer
|
||||
sublabel: Business Actor
|
||||
tag: Business Actor
|
||||
description: >
|
||||
A person or small business holding one or more policies
|
||||
with Halbrook Mutual.
|
||||
insurant:
|
||||
type: external
|
||||
label: Insurant
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
The role a customer plays while a claim is open; reports
|
||||
damage and receives the payment.
|
||||
claims-handler:
|
||||
type: external
|
||||
label: Claims handler
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
Employee of the claims department who accepts, valuates
|
||||
and approves claims.
|
||||
handle-claim:
|
||||
type: backend
|
||||
label: Handle Claim
|
||||
sublabel: Business Process
|
||||
tag: Business Process
|
||||
description: >
|
||||
Register, accept, valuate and pay a claim; the core process
|
||||
of the claims department.
|
||||
diagram: 0100-business/handle-claim.archify
|
||||
claim-registration:
|
||||
type: frontend
|
||||
label: Claim Registration
|
||||
sublabel: Business Service
|
||||
tag: Business Service
|
||||
description: >
|
||||
Lets an insurant report damage by phone, mail or the
|
||||
customer portal.
|
||||
claim-payment:
|
||||
type: frontend
|
||||
label: Claim Payment
|
||||
sublabel: Business Service
|
||||
tag: Business Service
|
||||
description: >
|
||||
Pays the valuated amount to the insurant or a repairer.
|
||||
@@ -0,0 +1,62 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
app-servers:
|
||||
type: backend
|
||||
label: App Servers
|
||||
sublabel: Node
|
||||
tag: Node
|
||||
description: >
|
||||
Two virtual machine hosts running the claims app, CRM and the
|
||||
portal back end.
|
||||
diagram: 0300-technology/infrastructure.archify
|
||||
db-cluster:
|
||||
type: database
|
||||
label: DB Cluster
|
||||
sublabel: Node
|
||||
brand: postgresql
|
||||
tag: Node
|
||||
description: >
|
||||
Primary and standby database servers for claims and CRM data.
|
||||
doc-store:
|
||||
type: database
|
||||
label: Document Store
|
||||
sublabel: System Software
|
||||
tag: System Software
|
||||
description: >
|
||||
Object storage for scans and photos; written by document
|
||||
management only.
|
||||
mainframe:
|
||||
type: backend
|
||||
label: Mainframe
|
||||
sublabel: Node
|
||||
tag: Node
|
||||
description: >
|
||||
Hosts policy administration and the nightly payment batch.
|
||||
mq:
|
||||
type: messagebus
|
||||
label: Message Queue
|
||||
sublabel: System Software
|
||||
tag: System Software
|
||||
description: >
|
||||
Queue manager between the app servers and the mainframe.
|
||||
db-service:
|
||||
type: cloud
|
||||
label: DB Service
|
||||
sublabel: Tech Service
|
||||
tag: Tech Service
|
||||
description: >
|
||||
Managed SQL access with backups, offered to all applications.
|
||||
dc-lan:
|
||||
type: messagebus
|
||||
label: Data Centre LAN
|
||||
sublabel: Network
|
||||
tag: Network
|
||||
description: >
|
||||
Internal network linking app servers, databases and mainframe.
|
||||
dmz:
|
||||
type: security
|
||||
label: DMZ Gateway
|
||||
sublabel: Network
|
||||
tag: Network
|
||||
description: >
|
||||
Reverse proxy and firewall between the internet and the LAN.
|
||||
@@ -0,0 +1,34 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Business actors and services
|
||||
output: business-services.html
|
||||
components:
|
||||
- ref: shipper
|
||||
pos: [40, 60]
|
||||
size: [130, 60]
|
||||
- ref: recipient
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: delivery-service
|
||||
pos: [250, 60]
|
||||
size: [130, 60]
|
||||
- ref: tracking-service
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: deliver-shipment
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: driver
|
||||
pos: [670, 60]
|
||||
size: [130, 60]
|
||||
- ref: customer-service
|
||||
pos: [670, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: delivery-service, to: shipper, label: serves, variant: emphasis }
|
||||
- { from: tracking-service, to: recipient, label: serves }
|
||||
- { from: deliver-shipment, to: delivery-service, label: realizes }
|
||||
- { from: deliver-shipment, to: tracking-service, label: realizes }
|
||||
- { from: driver, to: deliver-shipment, label: assigned to }
|
||||
- { from: customer-service, to: tracking-service, label: assigned to }
|
||||
@@ -0,0 +1,25 @@
|
||||
schema_version: 2
|
||||
diagram_type: workflow
|
||||
meta:
|
||||
title: Deliver Shipment
|
||||
output: deliver-shipment.html
|
||||
viewBox: [840, 534]
|
||||
lanes:
|
||||
- { id: customer-lane, label: Shipper and recipient }
|
||||
- { id: driver-lane, label: Driver }
|
||||
- { id: hub-lane, label: Hub }
|
||||
mainPath: [book, pick-up, sort, line-haul, last-mile, pod-step]
|
||||
nodes:
|
||||
- { id: book, lane: customer-lane, col: 0, type: external, label: Book shipment, tag: Business Event }
|
||||
- { id: pick-up, lane: driver-lane, col: 1, type: backend, label: Pick up, tag: Business Process }
|
||||
- { id: sort, lane: hub-lane, col: 2, type: backend, label: Sort at hub, tag: Business Process }
|
||||
- { id: line-haul, lane: hub-lane, col: 3, type: backend, label: Line haul, tag: Business Process }
|
||||
- { id: last-mile, lane: driver-lane, col: 4, type: backend, label: Last mile, tag: Business Process }
|
||||
- { id: pod-step, lane: customer-lane, col: 5, type: security, label: Delivered, sublabel: proof of delivery, tag: Business Process }
|
||||
edges:
|
||||
- { from: book, to: pick-up, label: triggers }
|
||||
- { from: pick-up, to: sort, label: parcel }
|
||||
- { from: sort, to: line-haul, label: trailer }
|
||||
- { from: line-haul, to: last-mile, label: route }
|
||||
- { from: last-mile, to: pod-step, label: hand over }
|
||||
- { from: last-mile, to: sort, label: not home, role: return, variant: dashed }
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Application cooperation
|
||||
output: application-cooperation.html
|
||||
decisions: [edi-gateway-in-front-of-tms]
|
||||
components:
|
||||
- ref: shipper
|
||||
pos: [40, 260]
|
||||
size: [130, 60]
|
||||
- ref: edi-gateway
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
decisions: [edi-gateway-in-front-of-tms]
|
||||
- ref: tms
|
||||
pos: [460, 260]
|
||||
size: [130, 60]
|
||||
- ref: driver-app
|
||||
pos: [460, 60]
|
||||
size: [130, 60]
|
||||
- ref: telematics
|
||||
pos: [460, 460]
|
||||
size: [130, 60]
|
||||
- ref: tracking-portal
|
||||
pos: [670, 260]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: shipper, to: edi-gateway, label: bookings, variant: emphasis }
|
||||
- { from: edi-gateway, to: tms, label: shipments, variant: emphasis }
|
||||
- { from: driver-app, to: tms, label: scans, labelAt: [525, 134], decisions: [broker-for-tracking-events] }
|
||||
- { from: telematics, to: tms, label: positions, decisions: [broker-for-tracking-events] }
|
||||
- { from: tms, to: tracking-portal, label: status }
|
||||
@@ -0,0 +1,34 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Application services and data
|
||||
output: application-services.html
|
||||
components:
|
||||
- ref: tracking-portal
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: tracking-svc
|
||||
pos: [250, 160]
|
||||
size: [130, 60]
|
||||
- ref: tms
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: driver-app
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
- ref: shipment
|
||||
pos: [250, 360]
|
||||
size: [130, 60]
|
||||
- ref: tracking-event
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
- ref: pod
|
||||
pos: [670, 360]
|
||||
size: [130, 60]
|
||||
connections:
|
||||
- { from: tracking-svc, to: tracking-portal, label: serves, variant: emphasis }
|
||||
- { from: tms, to: tracking-svc, label: realizes }
|
||||
- { from: driver-app, to: tms, label: uses }
|
||||
- { from: tms, to: shipment, label: accesses }
|
||||
- { from: tms, to: tracking-event, label: accesses, labelAt: [525, 234] }
|
||||
- { from: driver-app, to: pod, label: accesses, labelAt: [735, 234] }
|
||||
@@ -0,0 +1,44 @@
|
||||
schema_version: 1
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Technology infrastructure
|
||||
output: infrastructure.html
|
||||
decisions: [broker-for-tracking-events]
|
||||
components:
|
||||
- ref: handhelds
|
||||
pos: [40, 160]
|
||||
size: [130, 60]
|
||||
- ref: telematics-units
|
||||
pos: [40, 360]
|
||||
size: [130, 60]
|
||||
- ref: mobile-network
|
||||
pos: [250, 260]
|
||||
size: [130, 60]
|
||||
- ref: cloud-cluster
|
||||
pos: [460, 160]
|
||||
size: [130, 60]
|
||||
- ref: message-broker
|
||||
pos: [460, 360]
|
||||
size: [130, 60]
|
||||
decisions: [broker-for-tracking-events]
|
||||
- ref: db-server
|
||||
pos: [670, 160]
|
||||
size: [130, 60]
|
||||
- ref: event-streaming
|
||||
pos: [670, 360]
|
||||
size: [130, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Field devices
|
||||
wraps: [handhelds, telematics-units]
|
||||
- kind: security-group
|
||||
label: Cloud platform
|
||||
wraps: [cloud-cluster, message-broker, db-server, event-streaming]
|
||||
connections:
|
||||
- { from: handhelds, to: mobile-network, label: LTE }
|
||||
- { from: telematics-units, to: mobile-network, label: LTE }
|
||||
- { from: mobile-network, to: cloud-cluster, label: HTTPS, variant: security }
|
||||
- { from: mobile-network, to: message-broker, label: MQTT, variant: security }
|
||||
- { from: cloud-cluster, to: message-broker, label: hosts, labelAt: [525, 234] }
|
||||
- { from: cloud-cluster, to: db-server, label: SQL }
|
||||
- { from: message-broker, to: event-streaming, label: realizes }
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: broker-for-tracking-events
|
||||
title: A message broker for tracking events
|
||||
status: accepted
|
||||
date: 2026-09-08
|
||||
elements: [message-broker, telematics, driver-app, tms, tracking-event]
|
||||
diagrams: [0300-technology/infrastructure.archify, 0200-application/application-cooperation.archify]
|
||||
context: >
|
||||
Handhelds and telematics units posted every scan and position
|
||||
straight to the TMS. At evening peaks the TMS fell behind and
|
||||
dropped events when a device retried over a weak signal.
|
||||
decision: >
|
||||
Devices publish tracking events to a Kafka message broker on
|
||||
the cloud cluster. The TMS and telematics read the topics at
|
||||
their own pace; events are kept for seven days for replay.
|
||||
consequences: >
|
||||
Spikes no longer reach the TMS. Status on the tracking portal
|
||||
can lag by seconds. Consumers must handle duplicate events,
|
||||
and the broker is one more platform service to run.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: edi-gateway-in-front-of-tms
|
||||
title: An EDI gateway in front of the TMS
|
||||
status: accepted
|
||||
date: 2026-09-22
|
||||
elements: [edi-gateway, tms, shipper, shipment]
|
||||
diagrams: [0200-application/application-cooperation.archify]
|
||||
context: >
|
||||
Large shippers send bookings as EDI files in three dialects.
|
||||
Each dialect was parsed inside the TMS, so a new shipper meant
|
||||
a TMS release.
|
||||
decision: >
|
||||
A separate EDI gateway receives, validates and translates
|
||||
every booking into one shipment message for the TMS. It also
|
||||
sends status messages back in the shipper's dialect.
|
||||
consequences: >
|
||||
New shippers are onboarded in the gateway alone. The TMS has
|
||||
one booking interface. A rejected booking now fails at the
|
||||
gateway, which customer service must be able to see.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
schema_version: 1
|
||||
decision:
|
||||
id: photo-proof-of-delivery
|
||||
title: Photo as proof of delivery
|
||||
status: proposed
|
||||
date: 2026-10-27
|
||||
elements: [pod, driver-app, handhelds]
|
||||
diagrams: [0200-application/application-services.archify, 0100-business/deliver-shipment.archify]
|
||||
context: >
|
||||
Most parcels are left at the door when nobody signs. Disputes
|
||||
about such parcels take customer service days to settle.
|
||||
decision: >
|
||||
The driver app takes a photo of every parcel left without a
|
||||
signature and stores it with the proof of delivery.
|
||||
consequences: >
|
||||
Disputes are settled from the photo. Photos are personal data
|
||||
and are deleted after 90 days. Uploads add mobile data cost.
|
||||
@@ -0,0 +1,59 @@
|
||||
schema_version: 1
|
||||
model:
|
||||
id: logistics-tracking
|
||||
name: Logistics Tracking
|
||||
description: >
|
||||
An ArchiMate example: a parcel carrier lets shippers and recipients
|
||||
track shipments. Business, application and technology layers are
|
||||
mapped to folders; each element's ArchiMate type is shown as its tag.
|
||||
diagram_type: architecture
|
||||
meta:
|
||||
title: Logistics Tracking - layered view
|
||||
output: main.html
|
||||
components:
|
||||
- ref: recipient
|
||||
pos: [40, 70]
|
||||
size: [170, 60]
|
||||
- ref: tracking-service
|
||||
pos: [270, 70]
|
||||
size: [170, 60]
|
||||
- ref: deliver-shipment
|
||||
pos: [500, 70]
|
||||
size: [170, 60]
|
||||
- ref: tracking-portal
|
||||
pos: [40, 270]
|
||||
size: [170, 60]
|
||||
- ref: tms
|
||||
pos: [270, 270]
|
||||
size: [170, 60]
|
||||
- ref: tracking-svc
|
||||
pos: [500, 270]
|
||||
size: [170, 60]
|
||||
- ref: mobile-network
|
||||
pos: [40, 470]
|
||||
size: [170, 60]
|
||||
- ref: cloud-cluster
|
||||
pos: [270, 470]
|
||||
size: [170, 60]
|
||||
- ref: event-streaming
|
||||
pos: [500, 470]
|
||||
size: [170, 60]
|
||||
boundaries:
|
||||
- kind: region
|
||||
label: Business layer
|
||||
wraps: [recipient, tracking-service, deliver-shipment]
|
||||
- kind: region
|
||||
label: Application layer
|
||||
wraps: [tracking-portal, tms, tracking-svc]
|
||||
- kind: region
|
||||
label: Technology layer
|
||||
wraps: [mobile-network, cloud-cluster, event-streaming]
|
||||
connections:
|
||||
- { from: tracking-service, to: recipient, label: serves, variant: emphasis }
|
||||
- { from: deliver-shipment, to: tracking-service, label: realizes }
|
||||
- { from: tracking-portal, to: recipient, label: serves, labelAt: [125, 197] }
|
||||
- { from: tracking-svc, to: deliver-shipment, label: serves, labelAt: [585, 197] }
|
||||
- { from: tms, to: tracking-svc, label: realizes }
|
||||
- { from: mobile-network, to: tracking-portal, label: serves, labelAt: [125, 397] }
|
||||
- { from: cloud-cluster, to: tms, label: serves, labelAt: [355, 397] }
|
||||
- { from: event-streaming, to: tracking-svc, label: serves, labelAt: [585, 397] }
|
||||
@@ -0,0 +1,67 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
tms:
|
||||
type: backend
|
||||
label: TMS
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Owns shipments, routes and hub plans; the source of truth for status.
|
||||
diagram: 0200-application/application-cooperation.archify
|
||||
tracking-portal:
|
||||
type: frontend
|
||||
label: Tracking portal
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Public site where shippers and recipients follow a shipment.
|
||||
edi-gateway:
|
||||
type: security
|
||||
label: EDI gateway
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Receives booking messages from large shippers and returns status.
|
||||
driver-app:
|
||||
type: frontend
|
||||
label: Driver app
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Scans parcels at every hand-over and captures proof of delivery.
|
||||
telematics:
|
||||
type: backend
|
||||
label: Telematics
|
||||
sublabel: App Component
|
||||
tag: App Component
|
||||
description: >
|
||||
Collects vehicle positions and turns them into arrival estimates.
|
||||
tracking-svc:
|
||||
type: backend
|
||||
label: Tracking service
|
||||
sublabel: App Service
|
||||
tag: App Service
|
||||
description: >
|
||||
Status and arrival estimate of one shipment, offered by the TMS.
|
||||
diagram: 0200-application/application-services.archify
|
||||
shipment:
|
||||
type: database
|
||||
label: Shipment
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
One booked parcel with sender, recipient, route and status.
|
||||
tracking-event:
|
||||
type: database
|
||||
label: Tracking event
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
A scan or position report; the history of a shipment.
|
||||
pod:
|
||||
type: database
|
||||
label: Proof of delivery
|
||||
sublabel: Data Object
|
||||
tag: Data Object
|
||||
description: >
|
||||
Signature or photo taken at the door; closes the shipment.
|
||||
@@ -0,0 +1,54 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
shipper:
|
||||
type: external
|
||||
label: Shipper
|
||||
sublabel: Business Actor
|
||||
tag: Business Actor
|
||||
description: >
|
||||
Books shipments, hands parcels over at pick-up and follows
|
||||
them until delivery.
|
||||
recipient:
|
||||
type: external
|
||||
label: Recipient
|
||||
sublabel: Business Actor
|
||||
tag: Business Actor
|
||||
description: >
|
||||
Receives the parcel; tracks it and may reschedule delivery.
|
||||
driver:
|
||||
type: external
|
||||
label: Driver
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
Collects parcels, drives the routes and captures proof of delivery.
|
||||
customer-service:
|
||||
type: external
|
||||
label: Customer service
|
||||
sublabel: Business Role
|
||||
tag: Business Role
|
||||
description: >
|
||||
Answers shipper and recipient questions about late or lost parcels.
|
||||
deliver-shipment:
|
||||
type: backend
|
||||
label: Deliver Shipment
|
||||
sublabel: Business Process
|
||||
tag: Business Process
|
||||
description: >
|
||||
Pick up, sort at hub, line haul, last mile and proof of delivery.
|
||||
diagram: 0100-business/deliver-shipment.archify
|
||||
delivery-service:
|
||||
type: backend
|
||||
label: Parcel delivery
|
||||
sublabel: Business Service
|
||||
tag: Business Service
|
||||
description: >
|
||||
What the carrier sells; a parcel collected and delivered on time.
|
||||
diagram: 0100-business/business-services.archify
|
||||
tracking-service:
|
||||
type: backend
|
||||
label: Shipment tracking
|
||||
sublabel: Business Service
|
||||
tag: Business Service
|
||||
description: >
|
||||
Where a parcel is and when it arrives, for shippers and recipients.
|
||||
@@ -0,0 +1,55 @@
|
||||
schema_version: 1
|
||||
elements:
|
||||
cloud-cluster:
|
||||
type: cloud
|
||||
label: Cloud cluster
|
||||
sublabel: Node
|
||||
brand: kubernetes
|
||||
tag: Node
|
||||
description: >
|
||||
Container cluster that runs the TMS, portal, EDI gateway and telematics.
|
||||
diagram: 0300-technology/infrastructure.archify
|
||||
message-broker:
|
||||
type: messagebus
|
||||
label: Message broker
|
||||
sublabel: System Software
|
||||
brand: apache-kafka
|
||||
tag: System Software
|
||||
description: >
|
||||
Carries tracking events from devices and vehicles to the applications.
|
||||
db-server:
|
||||
type: database
|
||||
label: Database server
|
||||
sublabel: System Software
|
||||
brand: postgresql
|
||||
tag: System Software
|
||||
description: >
|
||||
Stores shipments, tracking events and proofs of delivery.
|
||||
event-streaming:
|
||||
type: backend
|
||||
label: Event streaming
|
||||
sublabel: Tech Service
|
||||
tag: Tech Service
|
||||
description: >
|
||||
Durable, ordered topics that applications publish to and read from.
|
||||
handhelds:
|
||||
type: external
|
||||
label: Handhelds
|
||||
sublabel: Device
|
||||
tag: Device
|
||||
description: >
|
||||
Scanners carried by drivers and hub staff; run the driver app.
|
||||
telematics-units:
|
||||
type: external
|
||||
label: Telematics units
|
||||
sublabel: Device
|
||||
tag: Device
|
||||
description: >
|
||||
GPS boxes in vans and trucks; report position every minute.
|
||||
mobile-network:
|
||||
type: security
|
||||
label: Mobile network
|
||||
sublabel: Network
|
||||
tag: Network
|
||||
description: >
|
||||
Carrier-grade mobile network that links devices to the cluster.
|
||||
@@ -77,6 +77,65 @@ The numbered folders are only for sorting; nothing depends on the names. A team
|
||||
|
||||
Component types that fit C4: people are `external`; the system itself and other systems are `cloud` (own) or `external` (others); containers are `frontend`, `backend`, `database`, `messagebus`, `security`; components inside a container use the same set, wrapped in a `region` boundary named after the container.
|
||||
|
||||
## arc42
|
||||
|
||||
[arc42](https://arc42.org) is a documentation template with twelve sections; four of them are views (diagrams), the rest are text. In a model, the views are folders numbered like their section, the decisions are section 9, and the text sections are `cards` (title plus short items) on the diagram they belong to. Card titles carry the section number so a reader finds each section.
|
||||
|
||||
```
|
||||
cinema-tickets/
|
||||
main.archify section 3 business context (the system and its neighbours);
|
||||
cards 1.1 Requirements, 1.2 Quality goals, 1.3 Stakeholders,
|
||||
2 Constraints, 4 Solution strategy, 11 Risks and technical debt,
|
||||
12 Glossary
|
||||
registry/
|
||||
people.archify-registry users and operators
|
||||
systems.archify-registry neighbouring systems
|
||||
building-blocks.archify-registry each block's description in black-box form:
|
||||
"Responsibility: ... Interfaces: ..." (section 5)
|
||||
0300-context/
|
||||
technical-context.archify channels and protocols; card 3 Domain interfaces
|
||||
0500-building-blocks/
|
||||
level-1.archify the system's white box; card 8 Crosscutting concepts
|
||||
booking.archify level 2 of one block, linked from its registry entry
|
||||
0600-runtime/
|
||||
book-seats.archify key scenarios as sequence diagrams
|
||||
0700-deployment/
|
||||
production.archify building blocks inside boundaries for nodes and zones;
|
||||
card 7 Mapping (block -> node)
|
||||
1000-quality/
|
||||
quality-tree.archify Quality -> characteristics -> scenarios;
|
||||
card 10 Quality scenarios (stimulus and response)
|
||||
decisions/ section 9, one *.archify-decision per decision
|
||||
```
|
||||
|
||||
Say where each section lives in `model.description`. Cards hold short lines, not prose: an arc42 document with long text belongs next to the model, not in it. `examples/arc42-cinema-tickets/`, `arc42-iot-telemetry/` and `arc42-team-wiki/` (the smallest) are complete.
|
||||
|
||||
## ArchiMate
|
||||
|
||||
[ArchiMate](https://www.opengroup.org/archimate-forum) models an enterprise in layers. Archify has no ArchiMate notation, so each element is an Archify component type with its ArchiMate type written out: in `tag` (searchable, shown in the focus panel) and in `sublabel` (shown on the box; Archify draws tags only in the focus panel).
|
||||
|
||||
```
|
||||
insurance-claims/
|
||||
main.archify layered view: Business, Application, Technology as three
|
||||
region boundaries, top to bottom, three elements each in the
|
||||
same columns, serves/realizes arrows between layers
|
||||
registry/
|
||||
business.archify-registry actors, roles, processes, services, events
|
||||
application.archify-registry components, services, interfaces, data objects
|
||||
technology.archify-registry nodes, system software, technology services, networks
|
||||
0100-business/
|
||||
business-layer.archify actors, roles and services
|
||||
handle-claim.archify the business process as a workflow (lanes per role)
|
||||
0200-application/
|
||||
application-structure.archify components, services, interfaces, data objects
|
||||
application-cooperation.archify how the components talk
|
||||
0300-technology/
|
||||
infrastructure.archify nodes and networks inside boundaries
|
||||
decisions/
|
||||
```
|
||||
|
||||
A mapping that reads well: actors and roles `external`; processes `backend`; business and application services `cloud` or `frontend`; application components `backend` or `frontend`; data objects `database`; nodes `backend`, `cloud` or `database`; networks `messagebus` or `security`. Name relationships in connection labels: serves, realizes, uses, accesses, assigned to, triggers. Keep type names short enough for the box (`App Component`, `Tech Service`). `examples/archimate-insurance-claims/`, `archimate-hr-onboarding/` and `archimate-logistics-tracking/` are complete.
|
||||
|
||||
## How the extension knows it is in a model
|
||||
|
||||
The nearest folder above a file that contains `main.archify` is the root. Under that root, at any depth: `*.archify` are member diagrams, `*.archify-registry` make up the registry, subfolders are groups. `node_modules` and `.git` are skipped.
|
||||
|
||||
@@ -30,3 +30,10 @@ tasks:
|
||||
- |
|
||||
bash ./.scripts/model/api/model--resolve.sh {{ .CLI_ARGS }}
|
||||
silent: true
|
||||
|
||||
graph:
|
||||
desc: 'The model as one graph, with queries -- --dir="<model folder or any file inside>" [--query="uses:api"] [--links]'
|
||||
cmds:
|
||||
- |
|
||||
bash ./.scripts/model/api/model--graph.sh {{ .CLI_ARGS }}
|
||||
silent: true
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# -------------------------------------------------------------------------------------
|
||||
# archify-vscode-ext Agent Skill (MIT)
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
. ./.scripts/model/lib/--index.sh
|
||||
|
||||
_model__model__graph "$@"
|
||||
@@ -8,3 +8,4 @@
|
||||
. ./.scripts/model/lib/-model--list.sh
|
||||
. ./.scripts/model/lib/-model--scaffold.sh
|
||||
. ./.scripts/model/lib/-model--resolve.sh
|
||||
. ./.scripts/model/lib/-model--graph.sh
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# -------------------------------------------------------------------------------------
|
||||
# archify-vscode-ext Agent Skill (MIT)
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
# Runs the "model-graph" action of the Node CLI. Flags are forwarded unchanged.
|
||||
_model__model__graph() {
|
||||
_model__node__run --action model-graph "$@"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// The model as one graph, as the extension's Graph page shows it, with the
|
||||
// same queries: answer "what connects to X", "the path from A to B", "what
|
||||
// a decision touches" or "which registry elements no diagram draws".
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { MAIN, findRoot, loadModel } from '../../services/model.mjs';
|
||||
import { buildGraph, runQuery } from '../../services/graph.mjs';
|
||||
|
||||
export const help = `model-graph — the whole model as one graph, narrowed by a query
|
||||
--dir <path> model folder, or any file/folder inside it (required)
|
||||
--query <text> database · type:backend · diagram:0200-* · decision:<id> · uses:api · near:api,2 · from:web to:db · unused · shared · -shared
|
||||
terms AND together, a leading - excludes; walks follow the diagrams' connections only
|
||||
--links also list the links from decisions and registries to their elements`;
|
||||
|
||||
export async function run(input) {
|
||||
if (!input.dir) return { success: false, error: '--dir is required: the model folder or any file inside it.', help };
|
||||
const target = path.resolve(String(input.dir));
|
||||
if (!fs.existsSync(target)) return { success: false, error: `${target} does not exist.` };
|
||||
const root = findRoot(target);
|
||||
if (!root) return { success: false, error: `No ${MAIN} in ${target} or any folder above it.` };
|
||||
const graph = buildGraph(loadModel(root));
|
||||
const query = typeof input.query === 'string' ? input.query : '';
|
||||
const result = runQuery(graph, query);
|
||||
const elements = graph.nodes.filter((n) => n.kind === 'element');
|
||||
const shown = graph.nodes.filter((n) => result.nodes.has(n.id));
|
||||
const edges = graph.edges.filter((e) => result.edges.has(e.id) && (input.links || e.kind === 'connection'));
|
||||
return {
|
||||
success: !result.message,
|
||||
root,
|
||||
query: query || undefined,
|
||||
message: result.message,
|
||||
summary: { elements: elements.length, connections: graph.edges.filter((e) => e.kind === 'connection').length, registries: graph.nodes.filter((n) => n.kind === 'registry').length, decisions: graph.decisions.length, diagrams: graph.diagrams.length, matching: shown.filter((n) => n.kind === 'element').length },
|
||||
path: result.path,
|
||||
nodes: shown.map((n) => ({ id: n.id, kind: n.kind, label: n.label, type: n.type, ...(n.sublabel ? { sublabel: n.sublabel } : {}), ...(n.kind === 'element' ? { shared: n.shared, diagrams: n.diagrams, decisions: n.decisions } : {}), ...(n.kind === 'decision' ? { status: n.status, file: n.file } : {}), ...(n.kind === 'registry' ? { file: n.file, elements: n.elements } : {}) })),
|
||||
connections: edges.map((e) => ({ from: e.from, to: e.to, kind: e.kind, ...(e.labels.length ? { label: e.labels.join(' / ') } : {}), ...(e.diagrams.length ? { diagrams: e.diagrams } : {}), ...(e.decisions.length ? { decisions: e.decisions } : {}) })),
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,12 @@ export const EXAMPLES = {
|
||||
'c4-standard-web-app': 'C4 - Standard Web App: single-page app, API, database, cache, file storage, identity provider',
|
||||
'c4-e-shop': 'C4 - E-Shop: web shop, API, worker, queue, payment and email providers',
|
||||
'c4-event-based-integration': 'C4 - Event-Based Integration: event bus, router, dead-letter queue, event store, consumers',
|
||||
'arc42-cinema-tickets': 'arc42 - Cinema Tickets: context, building blocks, runtime and deployment; seat booking with payment',
|
||||
'arc42-iot-telemetry': 'arc42 - IoT Telemetry: devices over MQTT, ingest, time-series store, alerting, dashboards',
|
||||
'arc42-team-wiki': 'arc42 - Team Wiki: a small self-hosted wiki, the arc42 sections at their smallest',
|
||||
'archimate-insurance-claims': 'ArchiMate - Insurance Claims: business, application and technology layers of claim handling',
|
||||
'archimate-hr-onboarding': 'ArchiMate - HR Onboarding: onboarding a new employee across HR, identity and IT services',
|
||||
'archimate-logistics-tracking': 'ArchiMate - Logistics Tracking: shipment tracking from pick-up to proof of delivery',
|
||||
};
|
||||
|
||||
export const help = `model-scaffold — create main.archify (and with --c4 the C4 folders), or copy an example
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as modelValidate from '../actions/model-validate/action.mjs';
|
||||
import * as modelList from '../actions/model-list/action.mjs';
|
||||
import * as modelScaffold from '../actions/model-scaffold/action.mjs';
|
||||
import * as modelResolve from '../actions/model-resolve/action.mjs';
|
||||
import * as modelGraph from '../actions/model-graph/action.mjs';
|
||||
import * as diagramValidate from '../actions/diagram-validate/action.mjs';
|
||||
import * as diagramRender from '../actions/diagram-render/action.mjs';
|
||||
import { toYaml } from '../services/yaml.mjs';
|
||||
@@ -15,6 +16,7 @@ const ACTIONS = {
|
||||
'model-list': modelList,
|
||||
'model-scaffold': modelScaffold,
|
||||
'model-resolve': modelResolve,
|
||||
'model-graph': modelGraph,
|
||||
'diagram-validate': diagramValidate,
|
||||
'diagram-render': diagramRender,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
// The model as one graph, as the extension's Graph page shows it: every element any
|
||||
// diagram draws plus every registry element, every connection once, plus one node
|
||||
// per decision and per registry file. The query language is the page's: terms AND
|
||||
// together, a leading "-" excludes, key:value terms filter or walk the graph.
|
||||
|
||||
import { NODE_COLLECTION } from './model.mjs';
|
||||
import { effectiveStatus } from './decisions.mjs';
|
||||
|
||||
const EDGE_COLLECTIONS = ['connections', 'edges', 'flows', 'transitions', 'messages'];
|
||||
const NODE_COLLECTIONS = [...new Set(Object.values(NODE_COLLECTION))];
|
||||
const str = (v) => (typeof v === 'string' && v.length ? v : undefined);
|
||||
const push = (list, value) => { if (!list.includes(value)) list.push(value); };
|
||||
|
||||
export const edgeId = (from, to) => `${from}>${to}`;
|
||||
export const decisionNodeId = (id) => `decision:${id}`;
|
||||
export const registryNodeId = (file) => `registry:${file}`;
|
||||
|
||||
export function buildGraph(model) {
|
||||
const nodes = new Map();
|
||||
const edges = new Map();
|
||||
for (const e of Object.values(model.registry.elements)) {
|
||||
nodes.set(e.id, { id: e.id, kind: 'element', label: e.label ?? e.id, sublabel: e.sublabel, type: e.type, brand: e.brand, shared: true, description: e.description?.trim() || undefined, file: e.file, line: e.line, link: e.diagram, diagrams: [], decisions: [] });
|
||||
}
|
||||
const diagrams = model.diagrams.filter((d) => d.resolved && !d.isMain).sort((a, b) => a.file.localeCompare(b.file));
|
||||
for (const d of diagrams) {
|
||||
const ids = new Set();
|
||||
for (const key of NODE_COLLECTIONS) {
|
||||
for (const item of Array.isArray(d.resolved[key]) ? d.resolved[key] : []) {
|
||||
if (!item || typeof item !== 'object') continue;
|
||||
const id = str(item.id);
|
||||
if (!id || ids.has(id)) continue;
|
||||
ids.add(id);
|
||||
let node = nodes.get(id);
|
||||
if (!node) {
|
||||
node = { id, kind: 'element', label: str(item.label) ?? id, sublabel: str(item.sublabel), type: str(item.type), brand: str(item.brand), shared: false, diagrams: [], decisions: [] };
|
||||
nodes.set(id, node);
|
||||
}
|
||||
push(node.diagrams, d.file);
|
||||
}
|
||||
}
|
||||
for (const key of EDGE_COLLECTIONS) {
|
||||
for (const item of Array.isArray(d.resolved[key]) ? d.resolved[key] : []) {
|
||||
const from = str(item?.from);
|
||||
const to = str(item?.to);
|
||||
if (!from || !to || !ids.has(from) || !ids.has(to)) continue;
|
||||
const id = edgeId(from, to);
|
||||
let edge = edges.get(id);
|
||||
if (!edge) { edge = { id, kind: 'connection', from, to, labels: [], diagrams: [], decisions: [] }; edges.set(id, edge); }
|
||||
if (str(item.label)) push(edge.labels, item.label);
|
||||
push(edge.diagrams, d.file);
|
||||
}
|
||||
}
|
||||
}
|
||||
const all = model.decisions.decisions;
|
||||
for (const d of Object.values(all)) for (const id of d.elements ?? []) if (nodes.has(id)) push(nodes.get(id).decisions, d.id);
|
||||
for (const [decisionId, uses] of Object.entries(model.decisionUsage ?? {})) {
|
||||
if (!all[decisionId]) continue;
|
||||
for (const use of uses) {
|
||||
if (use.attachedTo?.kind === 'node' && nodes.has(use.attachedTo.id)) push(nodes.get(use.attachedTo.id).decisions, decisionId);
|
||||
if (use.attachedTo?.kind === 'edge') { const edge = edges.get(edgeId(use.attachedTo.from, use.attachedTo.to)); if (edge) push(edge.decisions, decisionId); }
|
||||
}
|
||||
}
|
||||
const byFile = new Map();
|
||||
for (const e of Object.values(model.registry.elements)) byFile.set(e.file, [...(byFile.get(e.file) ?? []), e.id]);
|
||||
for (const { file } of model.registry.files ?? []) if (!byFile.has(file)) byFile.set(file, []);
|
||||
for (const [file, ids] of byFile) {
|
||||
const id = registryNodeId(file);
|
||||
nodes.set(id, { id, kind: 'registry', label: file.replace(/\.archify-registry$/, ''), type: 'registry', shared: true, file, diagrams: [], decisions: [], elements: ids });
|
||||
for (const element of ids) { const e = edgeId(id, element); edges.set(e, { id: e, kind: 'defines', from: id, to: element, labels: [], diagrams: [], decisions: [] }); }
|
||||
}
|
||||
for (const d of Object.values(all)) {
|
||||
const id = decisionNodeId(d.id);
|
||||
nodes.set(id, { id, kind: 'decision', label: d.title ?? d.id, sublabel: d.id, type: 'decision', shared: true, description: [d.context, d.decision].filter(Boolean).join('\n\n') || undefined, file: d.file, diagrams: [...(d.diagrams ?? [])], decisions: [], status: effectiveStatus(d, all).status });
|
||||
}
|
||||
for (const node of [...nodes.values()]) {
|
||||
if (node.kind !== 'element') continue;
|
||||
for (const decision of node.decisions) { if (!all[decision]) continue; const e = edgeId(decisionNodeId(decision), node.id); edges.set(e, { id: e, kind: 'decides', from: decisionNodeId(decision), to: node.id, labels: [], diagrams: [], decisions: [] }); }
|
||||
}
|
||||
for (const d of Object.values(all)) for (const other of d.supersedes ?? []) { if (!all[other]) continue; const e = edgeId(decisionNodeId(d.id), decisionNodeId(other)); edges.set(e, { id: e, kind: 'supersedes', from: decisionNodeId(d.id), to: decisionNodeId(other), labels: ['supersedes'], diagrams: [], decisions: [] }); }
|
||||
return {
|
||||
nodes: [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
||||
edges: [...edges.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
||||
decisions: Object.values(all).map((d) => ({ id: d.id, title: d.title, status: effectiveStatus(d, all).status, file: d.file })).sort((a, b) => a.id.localeCompare(b.id)),
|
||||
diagrams: diagrams.map((d) => ({ path: d.file, title: d.resolved?.meta?.title, kind: d.diagramType })),
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------- queries
|
||||
|
||||
const WALK_KEYS = new Set(['uses', 'near', 'from', 'to']);
|
||||
const FIELD_KEYS = new Set(['type', 'diagram', 'decision', 'shared', 'unused', 'text']);
|
||||
|
||||
export function parseQuery(text) {
|
||||
const tokens = String(text ?? '').trim().split(/\s+/).filter(Boolean);
|
||||
const terms = [];
|
||||
for (let i = 0; i < tokens.length; i += 1) {
|
||||
let token = tokens[i];
|
||||
const negate = token.startsWith('-') && token.length > 1;
|
||||
if (negate) token = token.slice(1);
|
||||
const colon = token.indexOf(':');
|
||||
let key = colon > 0 ? token.slice(0, colon).toLowerCase() : token.toLowerCase();
|
||||
let value = colon > 0 ? token.slice(colon + 1) : '';
|
||||
if (colon > 0 && !WALK_KEYS.has(key) && !FIELD_KEYS.has(key)) { key = 'text'; value = token; }
|
||||
else if (colon < 0 && key !== 'shared' && key !== 'unused') { key = 'text'; value = token; }
|
||||
if (key === 'near' && !value.includes(',') && /^\d+$/.test(tokens[i + 1] ?? '')) { value = `${value},${tokens[i + 1]}`; i += 1; }
|
||||
if (!value && key === 'text') continue;
|
||||
terms.push({ key, value, negate });
|
||||
}
|
||||
return terms;
|
||||
}
|
||||
|
||||
const lower = (s) => (s ?? '').toLowerCase();
|
||||
const isElement = (n) => n.kind === 'element';
|
||||
const isConnection = (e) => e.kind === 'connection';
|
||||
|
||||
export function findNode(graph, ref) {
|
||||
const elements = graph.nodes.filter(isElement);
|
||||
return elements.find((n) => n.id === ref) ?? elements.find((n) => n.id.toLowerCase() === ref.toLowerCase()) ?? elements.find((n) => n.label.toLowerCase() === ref.toLowerCase());
|
||||
}
|
||||
|
||||
function diagramMatches(file, pattern) {
|
||||
const bare = file.replace(/\.archify$/, '');
|
||||
const wanted = pattern.replace(/\.archify$/, '');
|
||||
if (/[*?]/.test(wanted)) { const re = new RegExp(`^${wanted.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.')}$`, 'i'); return re.test(bare) || re.test(file); }
|
||||
const l = wanted.toLowerCase();
|
||||
const b = bare.toLowerCase();
|
||||
return b === l || b.startsWith(l) || b.includes(`/${l}`);
|
||||
}
|
||||
|
||||
function neighbours(graph) {
|
||||
const map = new Map(graph.nodes.map((n) => [n.id, new Set()]));
|
||||
for (const e of graph.edges.filter(isConnection)) { map.get(e.from)?.add(e.to); map.get(e.to)?.add(e.from); }
|
||||
return map;
|
||||
}
|
||||
|
||||
function within(graph, start, hops) {
|
||||
const adjacency = neighbours(graph);
|
||||
let frontier = new Set([start]);
|
||||
const seen = new Set([start]);
|
||||
for (let i = 0; i < hops && frontier.size; i += 1) {
|
||||
const next = new Set();
|
||||
for (const id of frontier) for (const other of adjacency.get(id) ?? []) if (!seen.has(other)) { seen.add(other); next.add(other); }
|
||||
frontier = next;
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
export function shortestPath(graph, from, to) {
|
||||
const attempt = (directed) => {
|
||||
const out = new Map(graph.nodes.map((n) => [n.id, []]));
|
||||
for (const e of graph.edges.filter(isConnection)) { out.get(e.from)?.push({ to: e.to, edge: e }); if (!directed) out.get(e.to)?.push({ to: e.from, edge: e }); }
|
||||
const previous = new Map();
|
||||
const queue = [from];
|
||||
const seen = new Set([from]);
|
||||
while (queue.length) {
|
||||
const id = queue.shift();
|
||||
if (id === to) break;
|
||||
for (const step of out.get(id) ?? []) { if (seen.has(step.to)) continue; seen.add(step.to); previous.set(step.to, { from: id, edge: step.edge }); queue.push(step.to); }
|
||||
}
|
||||
if (!seen.has(to)) return undefined;
|
||||
const nodes = [to]; const edges = [];
|
||||
for (let at = to; at !== from;) { const step = previous.get(at); edges.unshift(step.edge.id); nodes.unshift(step.from); at = step.from; }
|
||||
return { nodes, edges };
|
||||
};
|
||||
if (from === to) return { nodes: [from], edges: [] };
|
||||
return attempt(true) ?? attempt(false);
|
||||
}
|
||||
|
||||
export function runQuery(graph, text) {
|
||||
const terms = parseQuery(text);
|
||||
let result = new Set(graph.nodes.map((n) => n.id));
|
||||
const notes = [];
|
||||
let from; let to; let path; let pathEdges;
|
||||
const matching = (term) => {
|
||||
const v = term.value;
|
||||
switch (term.key) {
|
||||
case 'text': { const l = v.toLowerCase(); return new Set(graph.nodes.filter((n) => [n.id, n.label, n.sublabel, n.brand, n.type].some((f) => lower(f).includes(l))).map((n) => n.id)); }
|
||||
case 'type': return new Set(graph.nodes.filter((n) => lower(n.type) === v.toLowerCase()).map((n) => n.id));
|
||||
case 'diagram': return new Set(graph.nodes.filter((n) => n.diagrams.some((d) => diagramMatches(d, v))).map((n) => n.id));
|
||||
case 'decision': {
|
||||
const ids = new Set(graph.nodes.filter((n) => n.decisions.includes(v)).map((n) => n.id));
|
||||
for (const e of graph.edges) if (e.decisions.includes(v)) { ids.add(e.from); ids.add(e.to); }
|
||||
if (!graph.decisions.some((d) => d.id === v)) notes.push(`No decision "${v}" in this model.`); else ids.add(decisionNodeId(v));
|
||||
return ids;
|
||||
}
|
||||
case 'shared': return new Set(graph.nodes.filter((n) => isElement(n) && n.shared).map((n) => n.id));
|
||||
case 'unused': return new Set(graph.nodes.filter((n) => isElement(n) && n.shared && !n.diagrams.length).map((n) => n.id));
|
||||
case 'uses': case 'near': {
|
||||
const [ref, hopsText] = term.key === 'near' ? v.split(',') : [v, '1'];
|
||||
const node = findNode(graph, ref);
|
||||
if (!node) { notes.push(`No element "${ref}".`); return new Set(); }
|
||||
return within(graph, node.id, Math.max(1, Math.min(Number(hopsText) || 1, 50)));
|
||||
}
|
||||
case 'from': from = v; return undefined;
|
||||
case 'to': to = v; return undefined;
|
||||
default: return undefined;
|
||||
}
|
||||
};
|
||||
const negated = [];
|
||||
for (const term of terms) {
|
||||
const ids = matching(term);
|
||||
if (!ids) continue;
|
||||
if (term.negate) { negated.push(ids); for (const id of ids) result.delete(id); }
|
||||
else result = new Set([...result].filter((id) => ids.has(id)));
|
||||
}
|
||||
if (terms.length) {
|
||||
for (const e of graph.edges) if (!isConnection(e) && result.has(e.to) && graph.nodes.some((n) => n.id === e.from && !isElement(n))) result.add(e.from);
|
||||
for (const ids of negated) for (const id of ids) result.delete(id);
|
||||
}
|
||||
if (from || to) {
|
||||
if (!from || !to) notes.push('A path needs both from: and to:.');
|
||||
else {
|
||||
const a = findNode(graph, from); const b = findNode(graph, to);
|
||||
if (!a || !b) notes.push(`No element "${!a ? from : to}".`);
|
||||
else {
|
||||
const found = shortestPath(graph, a.id, b.id);
|
||||
if (!found) notes.push(`No path from ${a.id} to ${b.id}.`);
|
||||
else { path = found.nodes; pathEdges = new Set(found.edges); result = new Set(found.nodes.filter((id) => result.has(id))); }
|
||||
}
|
||||
if (!path) result = new Set();
|
||||
}
|
||||
}
|
||||
const edges = new Set();
|
||||
for (const e of graph.edges) { if (!result.has(e.from) || !result.has(e.to)) continue; if (pathEdges && !pathEdges.has(e.id)) continue; edges.add(e.id); }
|
||||
return { nodes: result, edges, path, message: notes[0], terms };
|
||||
}
|
||||
@@ -69,6 +69,28 @@ try {
|
||||
assert(/referencedBy:\n\s+- 0200-containers\/online-shop\.archify/.test(r.stdout), 'referencedBy missing');
|
||||
});
|
||||
|
||||
check('model-graph example: 14 elements, 17 connections, decisions and registries as nodes', ['--action', 'model-graph', '--dir', example], (r, notes) => {
|
||||
assert(r.status === 0, `exit ${r.status}`);
|
||||
assert(yamlValue(r.stdout, ' elements') === '14' && yamlValue(r.stdout, ' connections') === '17', 'expected 14 elements and 17 connections');
|
||||
assert(yamlValue(r.stdout, ' registries') === '3' && yamlValue(r.stdout, ' decisions') === '3', 'expected 3 registries and 3 decisions');
|
||||
assert(/- id: decision:queue-between-api-and-worker\n\s+kind: decision/.test(r.stdout), 'decision node missing');
|
||||
assert(/- id: registry:registry\/containers\.archify-registry\n\s+kind: registry/.test(r.stdout), 'registry node missing');
|
||||
notes.push('14 elements, 17 connections, 3 registries, 3 decisions');
|
||||
});
|
||||
check('model-graph example: from:customer to:db is the path customer → web → api → db', ['--action', 'model-graph', '--dir', example, '--query', 'from:customer to:db'], (r, notes) => {
|
||||
assert(r.status === 0, `exit ${r.status}`);
|
||||
assert(/path:\n\s+- customer\n\s+- web\n\s+- api\n\s+- db/.test(r.stdout), 'path missing');
|
||||
assert(yamlValue(r.stdout, ' matching') === '4', 'expected 4 matching elements');
|
||||
notes.push('path of 4');
|
||||
});
|
||||
check('model-graph example: uses:api is api and its neighbours; an unknown element is reported', ['--action', 'model-graph', '--dir', example, '--query', 'uses:api -type:decision -type:registry'], (r, notes) => {
|
||||
assert(r.status === 0, `exit ${r.status}`);
|
||||
assert(yamlValue(r.stdout, ' matching') === '5', 'expected api plus 4 neighbours');
|
||||
const unknown = cliRun(['--action', 'model-graph', '--dir', example, '--query', 'uses:nothing']);
|
||||
assert(unknown.status !== 0 && /No element \\?"nothing\\?"/.test(unknown.stdout), 'unknown element not reported');
|
||||
notes.push('5 elements; unknown element reported');
|
||||
});
|
||||
|
||||
check('model-validate from a file inside the model', ['--action', 'model-validate', '--dir', path.join(example, '0300-components', 'api.archify')], (r) => {
|
||||
assert(r.status === 0, `exit ${r.status}`);
|
||||
assert(yamlValue(r.stdout, 'mode') === 'model', 'root was not found from a file inside');
|
||||
@@ -184,17 +206,19 @@ try {
|
||||
assert(r.status === 0, `exit ${r.status}`);
|
||||
});
|
||||
|
||||
// The shipped examples: the same three the extension offers under "Archify | New Model…".
|
||||
// The shipped examples: the same nine the extension offers under "Archify | New Model…".
|
||||
const examplesDir = path.join(skill, 'examples');
|
||||
const exampleNames = fs.readdirSync(examplesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
||||
check('examples/ holds exactly the three C4 examples', [], () => {
|
||||
assert(exampleNames.join(',') === 'c4-e-shop,c4-event-based-integration,c4-standard-web-app', `unexpected examples: ${exampleNames.join(',')}`);
|
||||
check('examples/ holds the nine examples: three C4, three arc42, three ArchiMate', [], () => {
|
||||
const expected = 'arc42-cinema-tickets,arc42-iot-telemetry,arc42-team-wiki,archimate-hr-onboarding,archimate-insurance-claims,archimate-logistics-tracking,c4-e-shop,c4-event-based-integration,c4-standard-web-app';
|
||||
assert(exampleNames.join(',') === expected, `unexpected examples: ${exampleNames.join(',')}`);
|
||||
});
|
||||
for (const name of exampleNames) {
|
||||
check(`example ${name} validates with 0 problems`, ['--action', 'model-validate', '--dir', path.join(examplesDir, name)], (r) => {
|
||||
assert(r.status === 0, `exit ${r.status}`);
|
||||
assert(yamlValue(r.stdout, ' errors') === '0' && yamlValue(r.stdout, ' warnings') === '0', 'example has problems');
|
||||
assert(yamlValue(r.stdout, ' diagrams') === '4', 'expected 4 diagrams');
|
||||
const diagrams = Number(yamlValue(r.stdout, ' diagrams'));
|
||||
assert(diagrams >= 4 && yamlValue(r.stdout, ' diagramsOk') === String(diagrams), `expected at least 4 diagrams, all ok (${diagrams})`);
|
||||
});
|
||||
}
|
||||
const fromExample = path.join(tmp, 'from-example');
|
||||
|
||||
Reference in new issue
Block a user