Compare commits

..
2 Commits
Author SHA1 Message Date
root-at-skic 7fd34fcce0 Merge pull request '0.11.1: the arc42 examples cover all twelve sections' (#43) from feat/arc42-all-sections into main 2026-11-08 13:10:38 -08:00
root-at-skic f4663c089d 0.11.1: the arc42 examples cover all twelve sections
Numbered cards for the text sections (1.1-1.3, 2, 4, 11, 12) on the
overview, domain interfaces on the technical context, black-box
descriptions for every building block, blocks mapped to nodes in the
deployment view, crosscutting concepts on level 1, and a 1000-quality
folder with the quality tree. The example test checks each section's card.
2026-11-08 23:10:24 +02:00
23 changed files with 511 additions and 91 deletions

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## 0.11.1 — 2026-11-08
- **The arc42 examples cover all twelve sections.** Each overview carries cards for 1.1 Requirements, 1.2 Quality goals, 1.3 Stakeholders, 2 Constraints, 4 Solution strategy, 11 Risks and technical debt and 12 Glossary; the technical context lists the domain interfaces (3); every building block has a black-box description, responsibility and interfaces (5); the deployment view places the building blocks on their nodes with a mapping (7); level 1 carries the crosscutting concepts (8); a new `1000-quality` folder draws the quality tree with its scenarios (10). Each model's description says where each section lives.
## 0.11.0 — 2026-11-08 ## 0.11.0 — 2026-11-08
- **arc42 and ArchiMate examples.** *Archify | New Model…* now offers nine examples, three per framework. **arc42** (Cinema Tickets, IoT Telemetry, Team Wiki): the overview is the business context with quality goals, constraints, quality scenarios, risks and a glossary as cards; then `0300-context`, `0500-building-blocks` (levels 1 and 2), `0600-runtime` (sequence diagrams), `0700-deployment`, and the decisions as section 9. **ArchiMate** (Insurance Claims, HR Onboarding, Logistics Tracking): the overview is a layered view of the business, application and technology layers; then `0100-business` (with the business process as a workflow), `0200-application` and `0300-technology`, one registry file per layer, each element's ArchiMate type under its name. - **arc42 and ArchiMate examples.** *Archify | New Model…* now offers nine examples, three per framework. **arc42** (Cinema Tickets, IoT Telemetry, Team Wiki): the overview is the business context with quality goals, constraints, quality scenarios, risks and a glossary as cards; then `0300-context`, `0500-building-blocks` (levels 1 and 2), `0600-runtime` (sequence diagrams), `0700-deployment`, and the decisions as section 9. **ArchiMate** (Insurance Claims, HR Onboarding, Logistics Tracking): the overview is a layered view of the business, application and technology layers; then `0100-business` (with the business process as a workflow), `0200-application` and `0300-technology`, one registry file per layer, each element's ArchiMate type under its name.
+1 -1
View File
@@ -126,7 +126,7 @@ online-shop/
- **Decisions.** One `*.archify-decision` file per architecture decision: id, title, status, date, the registry elements and diagrams it is about, and prose for context, decision and consequences. Diagrams point at decisions with `decisions: [<id>]` on the diagram, a component or a connection. They show as a **Decisions** panel on the `main.archify` page, a **Decisions** rail panel under each diagram (hover lights up what the decision is attached to), on element cards and in the element dialog, and a decision opens as its own page. *Archify | New Decision…* creates one; links are checked in Problems. - **Decisions.** One `*.archify-decision` file per architecture decision: id, title, status, date, the registry elements and diagrams it is about, and prose for context, decision and consequences. Diagrams point at decisions with `decisions: [<id>]` on the diagram, a component or a connection. They show as a **Decisions** panel on the `main.archify` page, a **Decisions** rail panel under each diagram (hover lights up what the decision is attached to), on element cards and in the element dialog, and a decision opens as its own page. *Archify | New Decision…* creates one; links are checked in Problems.
- **Registry view.** A `.archify-registry` file opens as its elements, as cards. Click one and a dialog shows its description, where it is defined and the diagrams that use it; click a diagram to open it. **View | Source** at the top right switches to the YAML, checked against the registry schema as you type. - **Registry view.** A `.archify-registry` file opens as its elements, as cards. Click one and a dialog shows its description, where it is defined and the diagrams that use it; click a diagram to open it. **View | Source** at the top right switches to the YAML, checked against the registry schema as you type.
- **Elements and Connectors.** Under every diagram, Archify's node index is titled **Elements**, and a **Connectors** panel next to it lists every connection as "from → to" with its label. Hovering highlights the connector in the diagram; clicking keeps it highlighted and scrolls to it. - **Elements and Connectors.** Under every diagram, Archify's node index is titled **Elements**, and a **Connectors** panel next to it lists every connection as "from → to" with its label. Hovering highlights the connector in the diagram; clicking keeps it highlighted and scrolls to it.
- **Starting one.** *Archify | New Model…* (right-click a folder) copies one of the shipped examples into a new folder, named as you choose: **C4 - Standard Web App** (single-page app, API, database, cache, storage, identity provider), **C4 - E-Shop** (web shop, API, worker, queue, payment and email providers) or **C4 - Event-Based Integration** (event bus, router, dead-letter queue, event store, consumers), each with context, container and component diagrams. **arc42 - Cinema Tickets**, **arc42 - IoT Telemetry** and **arc42 - Team Wiki** follow arc42's sections: the overview is the business context with goals, constraints, quality scenarios, risks and a glossary as cards, then `0300-context`, `0500-building-blocks`, `0600-runtime` (sequence diagrams) and `0700-deployment`, with section 9 as decisions. **ArchiMate - Insurance Claims**, **ArchiMate - HR Onboarding** and **ArchiMate - Logistics Tracking** are layered: the overview is a layered view, then `0100-business`, `0200-application` and `0300-technology`, one registry file per layer, each element's ArchiMate type as its tag. Every example has a registry and decisions. For an empty model, *Archify | New Diagram… → Model* creates just a `main.archify`. - **Starting one.** *Archify | New Model…* (right-click a folder) copies one of the shipped examples into a new folder, named as you choose: **C4 - Standard Web App** (single-page app, API, database, cache, storage, identity provider), **C4 - E-Shop** (web shop, API, worker, queue, payment and email providers) or **C4 - Event-Based Integration** (event bus, router, dead-letter queue, event store, consumers), each with context, container and component diagrams. **arc42 - Cinema Tickets**, **arc42 - IoT Telemetry** and **arc42 - Team Wiki** cover all twelve arc42 sections: the overview is the business context (3) with requirements, quality goals, stakeholders, constraints, solution strategy, risks and technical debt, and a glossary as numbered cards; then `0300-context` (domain interfaces), `0500-building-blocks` (black boxes, crosscutting concepts), `0600-runtime` (sequence diagrams), `0700-deployment` (blocks mapped to nodes) and `1000-quality` (quality tree), with section 9 as decisions. **ArchiMate - Insurance Claims**, **ArchiMate - HR Onboarding** and **ArchiMate - Logistics Tracking** are layered: the overview is a layered view, then `0100-business`, `0200-application` and `0300-technology`, one registry file per layer, each element's ArchiMate type as its tag. Every example has a registry and decisions. For an empty model, *Archify | New Diagram… → Model* creates just a `main.archify`.
- **The graph.** The **Model | Graph | Source** switch at the top of the `main.archify` page (next to **Export** when the file has a diagram) replaces the page with the whole model as one graph: every element any diagram draws plus every registry element, every connection between them, each once. Registry elements have a solid border, inline ones a dashed border; colours are Archify's type colours and the legend chips at the bottom hide or show a type. **Decisions** are nodes too (tags coloured by status, dotted links to the elements they are about, dashed links to what they supersede), and so are **registries** (one node per file, dotted links to the elements it defines); their chips hide them when the picture gets busy. The layout is **Layered** by default, like Archify's stack: clients on the left, data on the right, registries in a row above, decisions in a row below, connections drawn orthogonally; click the button for a **Force** layout instead. Drag nodes, scroll to zoom, **Fit** (or <kbd>0</kbd>) frames what matches, **Labels** toggles connection labels, the dotted **Links** from decisions and registries to their elements are on by default and can be switched off, in which case only the selected element's show (hovering an element lights up its connections), the sun/moon button switches the page between its dark and light theme (remembered, shared with the diagram pages' theme choice), **Export PNG** saves the picture. On a diagram page the switch reads **Diagram | Graph | Source** and Graph opens the graph already narrowed to that diagram; **Source** on the graph page goes back to `main.archify` in its Source view. *Archify | Open Model Graph* does the same from the Command Palette or Explorer. - **The graph.** The **Model | Graph | Source** switch at the top of the `main.archify` page (next to **Export** when the file has a diagram) replaces the page with the whole model as one graph: every element any diagram draws plus every registry element, every connection between them, each once. Registry elements have a solid border, inline ones a dashed border; colours are Archify's type colours and the legend chips at the bottom hide or show a type. **Decisions** are nodes too (tags coloured by status, dotted links to the elements they are about, dashed links to what they supersede), and so are **registries** (one node per file, dotted links to the elements it defines); their chips hide them when the picture gets busy. The layout is **Layered** by default, like Archify's stack: clients on the left, data on the right, registries in a row above, decisions in a row below, connections drawn orthogonally; click the button for a **Force** layout instead. Drag nodes, scroll to zoom, **Fit** (or <kbd>0</kbd>) frames what matches, **Labels** toggles connection labels, the dotted **Links** from decisions and registries to their elements are on by default and can be switched off, in which case only the selected element's show (hovering an element lights up its connections), the sun/moon button switches the page between its dark and light theme (remembered, shared with the diagram pages' theme choice), **Export PNG** saves the picture. On a diagram page the switch reads **Diagram | Graph | Source** and Graph opens the graph already narrowed to that diagram; **Source** on the graph page goes back to `main.archify` in its Source view. *Archify | Open Model Graph* does the same from the Command Palette or Explorer.
- **Queries.** Type a few words in the box (<kbd>/</kbd> focuses it); matches stay bright, the rest dims, the count shows "9 of 14 elements". Terms combine with AND and a leading `-` excludes: `database` (id, label, sublabel, brand or type contain it), `type:backend`, `diagram:0200-*` (what those diagrams draw), `decision:<id>` (what a decision is about or is attached to), `uses:api` (an element and its neighbours), `near:api,2` (within two steps), `from:web to:db` (the shortest path, following the arrows first), `unused` (registry elements no diagram draws), `shared` and `-shared` (registry or inline elements only), `type:decision` and `type:registry` for the decision and registry nodes. Decisions and registries follow the elements they are attached to into a result; `-type:decision` or `-type:registry` leaves them out. Walks and paths follow the diagrams' connections only, so a registry is never a shortcut between its elements. The **?** button lists them; clicking an example runs it. - **Queries.** Type a few words in the box (<kbd>/</kbd> focuses it); matches stay bright, the rest dims, the count shows "9 of 14 elements". Terms combine with AND and a leading `-` excludes: `database` (id, label, sublabel, brand or type contain it), `type:backend`, `diagram:0200-*` (what those diagrams draw), `decision:<id>` (what a decision is about or is attached to), `uses:api` (an element and its neighbours), `near:api,2` (within two steps), `from:web to:db` (the shortest path, following the arrows first), `unused` (registry elements no diagram draws), `shared` and `-shared` (registry or inline elements only), `type:decision` and `type:registry` for the decision and registry nodes. Decisions and registries follow the elements they are attached to into a result; `-type:decision` or `-type:registry` leaves them out. Walks and paths follow the diagrams' connections only, so a registry is never a shortcut between its elements. The **?** button lists them; clicking an example runs it.
- **Side panel.** Click an element for its description, type, where it is defined (**Registry** opens the file at the line), the diagrams that draw it (click to open), its connections and its decisions (click to open). **Neighbours** runs `uses:` for it. Click a connection for its labels, the diagrams that draw it and its decisions. Click a decision node for its status, context and decision text, the elements and connections it is about, and **Open decision**; a registry node for the elements it defines, the ones no diagram draws yet, and **Open registry**. - **Side panel.** Click an element for its description, type, where it is defined (**Registry** opens the file at the line), the diagrams that draw it (click to open), its connections and its decisions (click to open). **Neighbours** runs `uses:` for it. Click a connection for its labels, the diagrams that draw it and its decisions. Click a decision node for its status, context and decision text, the elements and connections it is about, and **Open decision**; a registry node for the elements it defines, the ones no diagram draws yet, and **Open registry**.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.11.0", "version": "0.11.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.11.0", "version": "0.11.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.20.4", "@codemirror/autocomplete": "^6.20.4",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"displayName": "Archify Diagram Viewer", "displayName": "Archify Diagram Viewer",
"description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.", "description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, …) and opens rendered Archify HTML.",
"version": "0.11.0", "version": "0.11.1",
"publisher": "root-at-skic", "publisher": "root-at-skic",
"license": "MIT", "license": "MIT",
"sponsor": { "sponsor": {
@@ -28,3 +28,12 @@ connections:
- { from: tickets, to: psp, label: REST webhook, variant: security } - { from: tickets, to: psp, label: REST webhook, variant: security }
- { from: tickets, to: mail, label: SMTP TLS } - { from: tickets, to: mail, label: SMTP TLS }
- { from: tickets, to: pos, label: REST VPN } - { 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"
@@ -58,7 +58,9 @@ cards:
- dot: amber - dot: amber
title: 8 Crosscutting concepts title: 8 Crosscutting concepts
items: 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" - "Times in UTC; shown in cinema local time"
- "Every write carries an idempotency key"
- "QR payload signed with an Ed25519 key" - "QR payload signed with an Ed25519 key"
- "Structured logs with order id as trace key" - "Structured logs with order id as trace key"
@@ -5,7 +5,10 @@ meta:
output: production.html output: production.html
components: components:
- ref: customer - ref: customer
pos: [40, 160] pos: [40, 260]
size: [130, 60]
- ref: mobile
pos: [40, 60]
size: [130, 60] size: [130, 60]
- id: ingress - id: ingress
type: security type: security
@@ -44,6 +47,9 @@ boundaries:
- kind: security-group - kind: security-group
label: Managed data zone label: Managed data zone
wraps: [seatlock, db] wraps: [seatlock, db]
- kind: region
label: Customer phones
wraps: [mobile]
- kind: region - kind: region
label: Cinema site label: Cinema site
wraps: [pos, scanner] wraps: [pos, scanner]
@@ -56,3 +62,13 @@ connections:
- { from: ticketing, to: db, label: TLS } - { from: ticketing, to: db, label: TLS }
- { from: possync, to: pos, label: VPN, variant: security } - { from: possync, to: pos, label: VPN, variant: security }
- { from: scanner, to: ingress, label: HTTPS } - { 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"
@@ -3,13 +3,14 @@ model:
id: cinema-tickets id: cinema-tickets
name: Cinema Tickets name: Cinema Tickets
description: > description: >
An arc42 example: online and mobile seat booking for a small An arc42 example: seat booking for the fictional Lantern Cinemas.
cinema chain. This overview is section 3 (business context) with 1 Requirements, quality goals, stakeholders; 2 Constraints;
cards for sections 1, 2, 10, 11 and 12. 0300-context holds the 4 Solution strategy; 11 Risks; 12 Glossary: cards on this overview.
technical context (3), 0500-building-blocks the building block 3 Context: this overview (business) and 0300-context (technical).
view (5), 0600-runtime the runtime view (6), 0700-deployment the 5 Building blocks: 0500-building-blocks, black boxes in registry/.
deployment view (7), decisions/ the architecture decisions (9). 6 Runtime: 0600-runtime. 7 Deployment: 0700-deployment.
Crosscutting concepts (8) are a card on the level-1 diagram. 8 Crosscutting concepts: card on 0500-building-blocks/level-1.
9 Decisions: decisions/. 10 Quality: 1000-quality.
diagram_type: architecture diagram_type: architecture
meta: meta:
title: Cinema Tickets - business context title: Cinema Tickets - business context
@@ -40,12 +41,26 @@ connections:
- { from: tickets, to: mail, label: tickets } - { from: tickets, to: mail, label: tickets }
- { from: tickets, to: pos, label: seat sales } - { from: tickets, to: pos, label: seat sales }
cards: 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 - dot: cyan
title: 1 Quality goals title: 1.2 Quality goals
items: items:
- "No seat is ever sold twice" - "No seat is ever sold twice"
- "Checkout under 2 s at Friday peak" - "Checkout under 2 s at Friday peak"
- "Door check-in works offline" - "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 - dot: slate
title: 2 Constraints title: 2 Constraints
items: items:
@@ -53,16 +68,20 @@ cards:
- "Card data only at the provider" - "Card data only at the provider"
- "Team of four; hosted in the EU" - "Team of four; hosted in the EU"
- dot: emerald - dot: emerald
title: 10 Quality scenarios title: 4 Solution strategy
items: items:
- "500 holds on one show in 1 min: no double sale" - "Node.js, PostgreSQL, Redis on managed EU k8s: fits a team of four"
- "Wi-Fi down at the door: scans go on, sync later" - "Three services by job: booking, tickets, POS sync"
- "Payment fails: seats free again within 8 min" - "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 - dot: rose
title: 11 Risks title: 11 Risks and technical debt
items: items:
- "POS vendor API is slow and undocumented" - "Risk: POS vendor API is slow and undocumented"
- "Forwarded QR e-mails: first scan wins" - "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 - dot: violet
title: 12 Glossary title: 12 Glossary
items: items:
@@ -6,52 +6,67 @@ elements:
sublabel: React sublabel: React
brand: react brand: react
description: > description: >
Programme, seat map and checkout in the browser. 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: mobile:
type: frontend type: frontend
label: Mobile App label: Mobile App
sublabel: iOS and Android sublabel: iOS and Android
description: > description: >
The same booking flow as the web shop, plus a ticket wallet. Responsibility: the web shop's booking flow plus a ticket wallet.
Interfaces: provides the iOS/Android UI; requires Booking API
(JSON).
scanner: scanner:
type: frontend type: frontend
label: Scanner App label: Scanner App
sublabel: door phones sublabel: door phones
description: > description: >
Reads QR codes at the door; checks signatures offline. Responsibility: reads QR codes at the door, checks signatures
offline. Interfaces: provides the usher UI; requires Ticket
Service (POST /checkins), queued while offline.
booking: booking:
type: backend type: backend
label: Booking API label: Booking API
sublabel: Node.js sublabel: Node.js
brand: node-js brand: node-js
description: > description: >
Shows, seat maps, seat holds and orders; the only writer of Responsibility: shows, seat maps, holds and orders; the only
the seat inventory. 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 diagram: 0500-building-blocks/booking.archify
ticketing: ticketing:
type: backend type: backend
label: Ticket Service label: Ticket Service
sublabel: QR, check-in sublabel: QR, check-in
description: > description: >
Issues signed QR tickets, sends them by e-mail and records Responsibility: issues signed QR tickets, mails them and records
check-ins at the door. check-ins. Interfaces: provides issue and POST /checkins;
requires Tickets DB and the e-mail service (SMTP).
possync: possync:
type: backend type: backend
label: POS Sync label: POS Sync
sublabel: adapter sublabel: adapter
description: > description: >
Exchanges seat sales with the box-office POS in both directions. Responsibility: exchanges seat sales with the box-office POS.
Interfaces: provides a REST adapter to the POS over VPN;
requires Tickets DB (SQL).
seatlock: seatlock:
type: database type: database
label: Seat Locks label: Seat Locks
sublabel: Redis sublabel: Redis
brand: redis brand: redis
description: > description: >
Short-lived seat holds with a TTL while the customer pays. 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: db:
type: database type: database
label: Tickets DB label: Tickets DB
sublabel: PostgreSQL sublabel: PostgreSQL
brand: postgresql brand: postgresql
description: > description: >
Shows, seats, orders and tickets; the source of truth. Responsibility: shows, seats, orders and tickets; the source of
truth. Interfaces: provides SQL to Booking API, Ticket Service
and POS Sync; requires nothing.
@@ -33,3 +33,13 @@ connections:
- { from: heat-pump, to: telemetry-platform, label: MQTT 8883, variant: emphasis, decisions: [mqtt-with-mutual-tls] } - { 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: push-service, label: HTTP/2 }
- { from: telemetry-platform, to: service-desk, label: webhook } - { 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"
@@ -73,5 +73,8 @@ cards:
title: 8 Crosscutting concepts title: 8 Crosscutting concepts
items: items:
- Device identity, one X.509 certificate per device - 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 - Idempotent writes, keyed by device id and timestamp
- Error handling, invalid readings to dead letters
- Observability, metrics and traces on every service - Observability, metrics and traces on every service
@@ -29,6 +29,12 @@ components:
- ref: device-registry - ref: device-registry
pos: [760, 260] pos: [760, 260]
size: [130, 60] size: [130, 60]
- ref: ops-dashboard
pos: [1030, 160]
size: [130, 60]
- ref: customer-app
pos: [490, 440]
size: [130, 60]
boundaries: boundaries:
- kind: region - kind: region
label: Kubernetes cluster, eu-central label: Kubernetes cluster, eu-central
@@ -36,9 +42,26 @@ boundaries:
- kind: security-group - kind: security-group
label: Managed DB zone label: Managed DB zone
wraps: [tsdb, device-registry] wraps: [tsdb, device-registry]
- kind: region
label: Ops network, managed Grafana
wraps: [ops-dashboard]
- kind: region
label: Homeowner phones
wraps: [customer-app]
connections: connections:
- { from: heat-pump, to: mqtt-broker, label: "8883", variant: emphasis } - { from: heat-pump, to: mqtt-broker, label: "8883", variant: emphasis }
- { from: mqtt-broker, to: ingest, label: in-cluster } - { from: mqtt-broker, to: ingest, label: in-cluster }
- { from: ingest, to: tsdb, label: "5432", variant: emphasis } - { from: ingest, to: tsdb, label: "5432", variant: emphasis }
- { from: ingest, to: rules, label: gRPC } - { from: ingest, to: rules, label: gRPC }
- { from: device-api, to: device-registry, label: "5432" } - { 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"
@@ -5,11 +5,14 @@ model:
description: > description: >
An arc42 example: a fictional heat pump maker collects device telemetry An arc42 example: a fictional heat pump maker collects device telemetry
over MQTT, stores it, raises alerts and shows it to operations and customers. over MQTT, stores it, raises alerts and shows it to operations and customers.
main.archify holds section 3 business context with cards for sections Where the 12 arc42 sections live:
1 goals, 2 constraints, 10 quality, 11 risks and 12 glossary; 1 introduction and goals, 2 constraints, 4 solution strategy,
0300-context the technical context; 0500-building-blocks section 5; 11 risks and 12 glossary are cards on this overview;
0600-runtime section 6; 0700-deployment section 7; decisions/ section 9; 3 business context is this overview, technical context in 0300-context;
the crosscutting concepts of section 8 are a card on level-1. 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 diagram_type: architecture
meta: meta:
title: Business context title: Business context
@@ -44,29 +47,47 @@ connections:
- { from: telemetry-platform, to: push-service, label: alerts } - { from: telemetry-platform, to: push-service, label: alerts }
- { from: telemetry-platform, to: service-desk, label: tickets } - { from: telemetry-platform, to: service-desk, label: tickets }
cards: 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 - dot: cyan
title: 1 Quality goals title: 1.2 Quality goals
items: items:
- Reliability, no telemetry lost once accepted - Reliability, no telemetry lost once accepted
- Timeliness, alert within 60 s of a fault - Timeliness, alert within 60 s of a fault
- Security, every device authenticated - 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 - dot: slate
title: 2 Constraints title: 2 Constraints
items: items:
- Devices speak MQTT only; firmware by a partner - Devices speak MQTT only; firmware by a partner
- Data stays in one EU region - Data stays in one EU region
- Team of six; PostgreSQL skills in house - Team of six; PostgreSQL skills in house
- dot: emerald - dot: amber
title: 10 Quality scenarios title: 4 Solution strategy
items: items:
- A fault code reaches the homeowner within 60 s - MQTT with mutual TLS for devices (security, ADR 0001)
- Broker restart, devices reconnect, no data lost - TimescaleDB for telemetry, SQL everywhere (ADR 0002)
- A stolen certificate is revoked within 15 min - Ingest acks only after commit (reliability)
- Rules engine on the ingest stream (timeliness)
- Split by flow, ingest vs read vs provisioning
- dot: rose - dot: rose
title: 11 Risks title: 11 Risks and technical debt
items: items:
- R1 fleet growth beyond one TSDB node - "Risk: fleet growth beyond one TSDB node"
- R2 partner firmware delays certificate rotation - "Risk: partner firmware delays cert rotation"
- "Debt: payload v1 decoder kept for old firmware"
- "Debt: rules configured in code, no UI"
- dot: violet - dot: violet
title: 12 Glossary title: 12 Glossary
items: items:
@@ -5,14 +5,18 @@ elements:
label: MQTT broker label: MQTT broker
sublabel: mutual TLS sublabel: mutual TLS
description: > description: >
Terminates device connections; one topic per device. Responsibility: terminates device connections, one topic per device.
Interfaces: provides MQTT 8883 with mTLS; requires certs and
revocation checks from Provisioning.
ingest: ingest:
type: backend type: backend
label: Ingest label: Ingest
sublabel: Go sublabel: Go
brand: go brand: go
description: > description: >
Decodes, validates and stores telemetry; forwards it to the rules. 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 diagram: 0500-building-blocks/ingest.archify
tsdb: tsdb:
type: database type: database
@@ -20,42 +24,52 @@ elements:
sublabel: TimescaleDB sublabel: TimescaleDB
brand: postgresql brand: postgresql
description: > description: >
Raw telemetry for 90 days and hourly rollups for 10 years. 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: rules:
type: backend type: backend
label: Rules engine label: Rules engine
sublabel: alerting sublabel: alerting
description: > description: >
Evaluates thresholds and fault codes; raises alerts. 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: provisioning:
type: security type: security
label: Provisioning label: Provisioning
sublabel: device certs sublabel: device certs
description: > description: >
Registers devices and issues their client certificates. 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: device-registry:
type: database type: database
label: Device registry label: Device registry
sublabel: PostgreSQL sublabel: PostgreSQL
brand: postgresql brand: postgresql
description: > description: >
Devices, owners, firmware versions and certificate state. Responsibility: devices, owners, firmware and certificate state.
Interfaces: provides SQL on 5432 to Ingest, Provisioning, Device API.
device-api: device-api:
type: backend type: backend
label: Device API label: Device API
sublabel: REST sublabel: REST
description: > description: >
Serves telemetry and device data to the customer app. Responsibility: serves telemetry and device data to the customer app.
Interfaces: provides REST over HTTPS; requires TSDB and device registry.
ops-dashboard: ops-dashboard:
type: frontend type: frontend
label: Ops dashboard label: Ops dashboard
sublabel: Grafana sublabel: Grafana
brand: grafana brand: grafana
description: > description: >
Fleet view for the operations team, read only on the TSDB. Responsibility: fleet view for the operations team.
Interfaces: provides web UI over HTTPS; requires read-only SQL on TSDB.
customer-app: customer-app:
type: frontend type: frontend
label: Customer app label: Customer app
sublabel: mobile sublabel: mobile
description: > description: >
Shows the homeowner their heat pump and its alerts. Responsibility: shows the homeowner their heat pump and alerts.
Interfaces: provides the mobile UI; requires Device API and push.
@@ -24,3 +24,11 @@ connections:
- { from: wiki-admin, to: wiki, label: kubectl } - { from: wiki-admin, to: wiki, label: kubectl }
- { from: wiki, to: idp, label: OIDC, variant: security } - { from: wiki, to: idp, label: OIDC, variant: security }
- { from: wiki, to: object-store, label: S3 API } - { 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"
@@ -42,6 +42,9 @@ cards:
- dot: amber - dot: amber
title: 8 Crosscutting concepts title: 8 Crosscutting concepts
items: items:
- Access - every request checked against space groups - "Security: OIDC session, every request checked against space groups"
- Logs - JSON to stdout, collected by the cluster - "Persistence: PostgreSQL is the only source of truth"
- Config - environment variables, secrets from the vault - "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"
@@ -36,8 +36,11 @@ boundaries:
label: Company cluster label: Company cluster
wraps: [ingress, web, api, db, backup-job] wraps: [ingress, web, api, db, backup-job]
- kind: security-group - kind: security-group
label: Namespace team-wiki label: App node pool
wraps: [web, api, db, backup-job] wraps: [web, api]
- kind: security-group
label: Data node
wraps: [db, backup-job]
connections: connections:
- { from: engineer, to: ingress, label: HTTPS, variant: emphasis } - { from: engineer, to: ingress, label: HTTPS, variant: emphasis }
- { from: ingress, to: web, label: static } - { from: ingress, to: web, label: static }
@@ -46,3 +49,12 @@ connections:
- { from: api, to: idp, label: OIDC, variant: security, labelAt: [525, 234] } - { from: api, to: idp, label: OIDC, variant: security, labelAt: [525, 234] }
- { from: backup-job, to: db, label: dump } - { from: backup-job, to: db, label: dump }
- { from: backup-job, to: object-store, label: S3 } - { 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"
+42 -23
View File
@@ -3,12 +3,14 @@ model:
id: team-wiki id: team-wiki
name: Team Wiki name: Team Wiki
description: > description: >
An arc42 example: a small self-hosted wiki for an engineering An arc42 example: a small self-hosted wiki for an engineering team.
team. Sections 1 goals, 2 constraints, 3 business context, 1 Requirements, quality goals, stakeholders; 2 Constraints;
10 quality, 11 risks and 12 glossary are on this overview; 4 Solution strategy; 11 Risks; 12 Glossary: cards on this overview.
0300-context holds 3 technical context, 0500-building-blocks 3 Context: this overview (business) and 0300-context (technical).
5 building blocks, 0600-runtime 6 runtime, 0700-deployment 5 Building blocks: 0500-building-blocks, black boxes in registry/.
7 deployment and decisions 9 architecture decisions. 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 diagram_type: architecture
meta: meta:
title: Team Wiki - business context title: Team Wiki - business context
@@ -36,31 +38,48 @@ connections:
- { from: wiki, to: idp, label: sign-in, variant: security } - { from: wiki, to: idp, label: sign-in, variant: security }
- { from: wiki, to: object-store, label: files } - { from: wiki, to: object-store, label: files }
cards: cards:
- dot: cyan - dot: orange
title: 1 Quality goals title: 1.1 Requirements
items: items:
- Findability - any page found in two searches - "Create, edit and version pages in team spaces"
- Security - only current staff can read - "Full-text search over every page a user may read"
- Operability - one person runs it in an hour a week - "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 - dot: slate
title: 2 Constraints title: 2 Constraints
items: items:
- Runs on the company Kubernetes cluster - "Runs on the company Kubernetes cluster"
- Sign-in only via the company identity provider - "Sign-in only via the company identity provider"
- dot: emerald - dot: emerald
title: 10 Quality scenarios title: 4 Solution strategy
items: items:
- A search over 5000 pages answers in under 1 s - "React, Node.js API, PostgreSQL: one stack the team knows"
- A leaver loses access within 5 minutes - "Three blocks plus a backup job: operability"
- The admin restores last night's state in 30 minutes - "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 - dot: rose
title: 11 Risks title: 11 Risks and technical debt
items: items:
- Identity provider outage blocks all sign-ins - "Risk: identity provider outage blocks all sign-ins"
- Backups untested until a restore drill is run - "Risk: backups untested until a restore drill is run"
- "Debt: attachments are not virus-scanned yet"
- dot: violet - dot: violet
title: 12 Glossary title: 12 Glossary
items: items:
- Space - pages of one team, mapped to a group - "Space: pages of one team, mapped to a group"
- Revision - one saved version of a page - "Revision: one saved version of a page"
- Attachment - a file stored beside a page - "Attachment: a file stored beside a page"
@@ -6,15 +6,17 @@ elements:
sublabel: React sublabel: React
brand: react brand: react
description: > description: >
Page editor, viewer and search box in the browser; talks Responsibility: page editor, viewer and search box in the
only to the API. browser. Interfaces: provides the UI over HTTPS; requires
the Wiki API (JSON).
api: api:
type: backend type: backend
label: Wiki API label: Wiki API
sublabel: REST sublabel: REST
description: > description: >
Pages, search, attachments and access checks; the only Responsibility: pages, search, attachments and access checks;
writer of the database. 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 diagram: 0500-building-blocks/wiki-api.archify
db: db:
type: database type: database
@@ -22,12 +24,14 @@ elements:
sublabel: PostgreSQL sublabel: PostgreSQL
brand: postgresql brand: postgresql
description: > description: >
Pages, revisions, users and the full-text index; the Responsibility: pages, revisions, users and the full-text
source of truth. index; the source of truth. Interfaces: provides SQL to the
API and dumps to the backup job.
backup-job: backup-job:
type: backend type: backend
label: Backup job label: Backup job
sublabel: nightly sublabel: nightly
description: > description: >
Dumps the database at 02:00 and writes it to the backup Responsibility: dumps the database at 02:00, keeps 30 days.
bucket; keeps 30 days. Interfaces: requires the database (pg_dump) and the backup
bucket (S3); provides none.
+12 -1
View File
@@ -29,7 +29,7 @@ function walk(dir: string, rel = ''): string[] {
// The folders each framework's examples must have, besides main.archify, registry/ and decisions/. // The folders each framework's examples must have, besides main.archify, registry/ and decisions/.
const FRAMEWORK_FOLDERS: Record<string, string[]> = { const FRAMEWORK_FOLDERS: Record<string, string[]> = {
c4: ['0100-context', '0200-containers', '0300-components'], c4: ['0100-context', '0200-containers', '0300-components'],
arc42: ['0300-context', '0500-building-blocks', '0600-runtime', '0700-deployment'], arc42: ['0300-context', '0500-building-blocks', '0600-runtime', '0700-deployment', '1000-quality'],
archimate: ['0100-business', '0200-application', '0300-technology'], archimate: ['0100-business', '0200-application', '0300-technology'],
}; };
@@ -95,6 +95,17 @@ for (const example of MODEL_EXAMPLES) {
} }
// Every registry element is used by some diagram. // Every registry element is used by some diagram.
for (const id of registry.elements.keys()) assert.ok(used.has(id), `element ${id} is never used`); for (const id of registry.elements.keys()) assert.ok(used.has(id), `element ${id} is never used`);
// arc42: every text section has a card somewhere, titled with its number.
if (example.folder.startsWith('arc42-')) {
const titles = diagrams.flatMap((file) => {
const cards = parse(read(file))?.cards;
return Array.isArray(cards) ? cards.map((card: { title?: string }) => String(card?.title ?? '')) : [];
});
for (const section of ['1.1', '1.2', '1.3', '2', '3', '4', '7', '8', '10', '11', '12']) {
assert.ok(titles.some((title) => title.startsWith(`${section} `)), `${example.folder}: no card for arc42 section ${section}`);
}
}
}); });
} }