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
## 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
- **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.
- **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.
- **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.
- **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**.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "archify-vscode-ext",
"version": "0.11.0",
"version": "0.11.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "archify-vscode-ext",
"version": "0.11.0",
"version": "0.11.1",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.20.4",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "archify-vscode-ext",
"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.",
"version": "0.11.0",
"version": "0.11.1",
"publisher": "root-at-skic",
"license": "MIT",
"sponsor": {
@@ -28,3 +28,12 @@ connections:
- { 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"
@@ -58,7 +58,9 @@ 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"
- "Every write carries an idempotency key"
- "QR payload signed with an Ed25519 key"
- "Structured logs with order id as trace key"
@@ -5,7 +5,10 @@ meta:
output: production.html
components:
- ref: customer
pos: [40, 160]
pos: [40, 260]
size: [130, 60]
- ref: mobile
pos: [40, 60]
size: [130, 60]
- id: ingress
type: security
@@ -44,6 +47,9 @@ boundaries:
- 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]
@@ -56,3 +62,13 @@ connections:
- { 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"
@@ -3,13 +3,14 @@ model:
id: cinema-tickets
name: Cinema Tickets
description: >
An arc42 example: online and mobile seat booking for a small
cinema chain. This overview is section 3 (business context) with
cards for sections 1, 2, 10, 11 and 12. 0300-context holds the
technical context (3), 0500-building-blocks the building block
view (5), 0600-runtime the runtime view (6), 0700-deployment the
deployment view (7), decisions/ the architecture decisions (9).
Crosscutting concepts (8) are a card on the level-1 diagram.
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
@@ -40,12 +41,26 @@ connections:
- { 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 Quality goals
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:
@@ -53,16 +68,20 @@ cards:
- "Card data only at the provider"
- "Team of four; hosted in the EU"
- dot: emerald
title: 10 Quality scenarios
title: 4 Solution strategy
items:
- "500 holds on one show in 1 min: no double sale"
- "Wi-Fi down at the door: scans go on, sync later"
- "Payment fails: seats free again within 8 min"
- "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
title: 11 Risks and technical debt
items:
- "POS vendor API is slow and undocumented"
- "Forwarded QR e-mails: first scan wins"
- "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:
@@ -6,52 +6,67 @@ elements:
sublabel: React
brand: react
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:
type: frontend
label: Mobile App
sublabel: iOS and Android
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:
type: frontend
label: Scanner App
sublabel: door phones
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:
type: backend
label: Booking API
sublabel: Node.js
brand: node-js
description: >
Shows, seat maps, seat holds and orders; the only writer of
the seat inventory.
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: >
Issues signed QR tickets, sends them by e-mail and records
check-ins at the door.
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: >
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:
type: database
label: Seat Locks
sublabel: Redis
brand: redis
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:
type: database
label: Tickets DB
sublabel: PostgreSQL
brand: postgresql
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: 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"
@@ -73,5 +73,8 @@ cards:
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
@@ -29,6 +29,12 @@ components:
- 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
@@ -36,9 +42,26 @@ boundaries:
- 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"
@@ -5,11 +5,14 @@ model:
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.
main.archify holds section 3 business context with cards for sections
1 goals, 2 constraints, 10 quality, 11 risks and 12 glossary;
0300-context the technical context; 0500-building-blocks section 5;
0600-runtime section 6; 0700-deployment section 7; decisions/ section 9;
the crosscutting concepts of section 8 are a card on level-1.
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
@@ -44,29 +47,47 @@ connections:
- { 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 Quality goals
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: emerald
title: 10 Quality scenarios
- dot: amber
title: 4 Solution strategy
items:
- A fault code reaches the homeowner within 60 s
- Broker restart, devices reconnect, no data lost
- A stolen certificate is revoked within 15 min
- 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
title: 11 Risks and technical debt
items:
- R1 fleet growth beyond one TSDB node
- R2 partner firmware delays certificate rotation
- "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:
@@ -5,14 +5,18 @@ elements:
label: MQTT broker
sublabel: mutual TLS
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:
type: backend
label: Ingest
sublabel: Go
brand: go
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
tsdb:
type: database
@@ -20,42 +24,52 @@ elements:
sublabel: TimescaleDB
brand: postgresql
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:
type: backend
label: Rules engine
sublabel: alerting
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:
type: security
label: Provisioning
sublabel: device certs
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:
type: database
label: Device registry
sublabel: PostgreSQL
brand: postgresql
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:
type: backend
label: Device API
sublabel: REST
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:
type: frontend
label: Ops dashboard
sublabel: Grafana
brand: grafana
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:
type: frontend
label: Customer app
sublabel: mobile
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, 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"
@@ -42,6 +42,9 @@ cards:
- dot: amber
title: 8 Crosscutting concepts
items:
- Access - every request checked against space groups
- Logs - JSON to stdout, collected by the cluster
- Config - environment variables, secrets from the vault
- "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"
@@ -36,8 +36,11 @@ boundaries:
label: Company cluster
wraps: [ingress, web, api, db, backup-job]
- kind: security-group
label: Namespace team-wiki
wraps: [web, api, db, backup-job]
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 }
@@ -46,3 +49,12 @@ connections:
- { 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"
+42 -23
View File
@@ -3,12 +3,14 @@ model:
id: team-wiki
name: Team Wiki
description: >
An arc42 example: a small self-hosted wiki for an engineering
team. Sections 1 goals, 2 constraints, 3 business context,
10 quality, 11 risks and 12 glossary are on this overview;
0300-context holds 3 technical context, 0500-building-blocks
5 building blocks, 0600-runtime 6 runtime, 0700-deployment
7 deployment and decisions 9 architecture decisions.
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
@@ -36,31 +38,48 @@ connections:
- { from: wiki, to: idp, label: sign-in, variant: security }
- { from: wiki, to: object-store, label: files }
cards:
- dot: cyan
title: 1 Quality goals
- dot: orange
title: 1.1 Requirements
items:
- Findability - any page found in two searches
- Security - only current staff can read
- Operability - one person runs it in an hour a week
- "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
- "Runs on the company Kubernetes cluster"
- "Sign-in only via the company identity provider"
- dot: emerald
title: 10 Quality scenarios
title: 4 Solution strategy
items:
- A search over 5000 pages answers in under 1 s
- A leaver loses access within 5 minutes
- The admin restores last night's state in 30 minutes
- "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
title: 11 Risks and technical debt
items:
- Identity provider outage blocks all sign-ins
- Backups untested until a restore drill is run
- "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
- "Space: pages of one team, mapped to a group"
- "Revision: one saved version of a page"
- "Attachment: a file stored beside a page"
@@ -6,15 +6,17 @@ elements:
sublabel: React
brand: react
description: >
Page editor, viewer and search box in the browser; talks
only to the API.
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: >
Pages, search, attachments and access checks; the only
writer of the database.
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
@@ -22,12 +24,14 @@ elements:
sublabel: PostgreSQL
brand: postgresql
description: >
Pages, revisions, users and the full-text index; the
source of truth.
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: >
Dumps the database at 02:00 and writes it to the backup
bucket; keeps 30 days.
Responsibility: dumps the database at 02:00, 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/.
const FRAMEWORK_FOLDERS: Record<string, string[]> = {
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'],
};
@@ -95,6 +95,17 @@ for (const example of MODEL_EXAMPLES) {
}
// 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`);
// 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}`);
}
}
});
}