Architecture decisions: files, diagram references, views and panels

- *.archify-decision files: id (unique, words), title, status, date,
  elements, diagrams, supersedes, and prose; parsed, merged and link-checked
  in the model index; diagnostics on the file
- diagrams point at decisions with decisions: [...] on the diagram, nodes
  and edges; stripped before Archify, unknown ids reported, completion,
  hover and go-to-definition for the ids and for elements/diagrams/
  supersedes in decision files
- Decisions panel on the main.archify page; Decisions rail panel under a
  diagram (hover lights up the node or connection); decisions on element
  cards and in the element dialog; decision view for the file, with a
  lost-model note outside a model
- Archify | New Decision… command and template; editor schemas; the three
  New Model examples carry decisions; docs, changelog, 0.8.0
This commit is contained in:
2026-10-19 23:13:05 +03:00
parent 33ebdd6328
commit ebf320a023
49 changed files with 1637 additions and 55 deletions
+17
View File
@@ -0,0 +1,17 @@
# An architecture decision of this model. The id is unique in the model;
# elements are registry ids, diagrams are paths from the model root.
schema_version: 1
decision:
id: __ID__
title: __TITLE__
status: proposed # proposed | accepted | superseded | rejected
date: __DATE__
elements: []
diagrams: []
supersedes: []
context: >
What was the situation, and what forced a decision.
decision: >
What was decided, in one or two sentences.
consequences: >
What becomes easier, what becomes harder, what must change.
@@ -3,6 +3,7 @@ diagram_type: architecture
meta:
title: Containers
output: containers.html
decisions: [postgres-as-source-of-truth]
components:
- ref: customer
pos: [40, 160]
@@ -22,6 +23,7 @@ components:
- ref: queue
pos: [460, 360]
size: [130, 60]
decisions: [queue-between-api-and-worker]
- ref: worker
pos: [670, 460]
size: [130, 60]
@@ -34,6 +36,6 @@ connections:
- { from: web, to: api, label: JSON }
- { from: api, to: cache, label: read }
- { from: api, to: db, label: SQL }
- { from: api, to: queue, label: enqueue, labelAt: [560, 290] }
- { from: api, to: queue, label: enqueue, labelAt: [560, 290], decisions: [queue-between-api-and-worker] }
- { from: queue, to: worker, label: poll }
- { from: worker, to: db, label: SQL, labelAt: [735, 420] }
@@ -0,0 +1,20 @@
schema_version: 1
decision:
id: postgres-as-source-of-truth
title: PostgreSQL as the single source of truth
status: accepted
date: 2026-09-02
elements: [db, api, worker]
diagrams: [0200-containers/online-shop.archify]
context: >
Orders, customers and the catalogue were spread over the
shop database and two spreadsheets kept by support.
Refunds were reconciled by hand every Monday.
decision: >
One PostgreSQL database owns orders, customers and the
catalogue. The API is the only writer. Everything else
(cache, search, reports) is derived and may be rebuilt.
consequences: >
Support loses the spreadsheets and gets the admin console.
Any new store must be declared derived or get its own
decision. Backups and migrations are the API team's job.
@@ -0,0 +1,20 @@
schema_version: 1
decision:
id: queue-between-api-and-worker
title: A queue between the API and the worker
status: accepted
date: 2026-09-16
elements: [api, queue, worker]
diagrams: [0200-containers/online-shop.archify]
context: >
Order confirmation emails and invoices were sent inside
the checkout request. A slow mail provider made checkout
slow; a failing one made checkout fail.
decision: >
The API enqueues a job per side effect on SQS. One worker
service consumes the queue and does the slow work.
Checkout returns as soon as the order is stored.
consequences: >
Emails arrive seconds later, not instantly. Jobs must be
idempotent because SQS delivers at least once. The worker
is the second place that writes to the database.
@@ -0,0 +1,20 @@
schema_version: 1
decision:
id: opensearch-for-product-search
title: OpenSearch for product search
status: proposed
date: 2026-10-19
elements: [api, worker, db]
diagrams: [0200-containers/online-shop.archify]
context: >
Catalogue search runs as SQL LIKE queries on the
database. Typos find nothing and the catalogue page is
the slowest page in the shop.
decision: >
Add a dedicated search service on OpenSearch. The API
queries it; the worker rebuilds the index from the
database after every catalogue change.
consequences: >
The index is derived data under decision 0001 and may be
rebuilt at any time. One more service to run. Search
results can lag the database by the rebuild interval.
@@ -43,4 +43,4 @@ connections:
- { from: router, to: store, label: append }
- { from: router, to: notifier, label: notify }
- { from: router, to: analytics, label: flatten }
- { from: router, to: dlq, label: failed, variant: dashed, labelDy: 24 }
- { from: router, to: dlq, label: failed, variant: dashed, labelDy: 24, decisions: [dead-letter-queue] }
@@ -0,0 +1,18 @@
schema_version: 1
decision:
id: dead-letter-queue
title: Failed events go to a dead-letter queue, never dropped
status: accepted
date: 2026-09-24
elements: [router, dlq, ops]
diagrams: [0200-containers/platform.archify, 0300-components/router.archify]
context: >
Events that failed schema validation were logged and lost.
Nobody noticed until a customer asked where an order went.
decision: >
The router moves every event it cannot process to a
dead-letter queue with the reason. Operations replays
them after the cause is fixed.
consequences: >
Nothing is lost silently. Someone has to watch the queue;
that is an on-call duty for operations.
@@ -32,7 +32,7 @@ boundaries:
connections:
- { from: user, to: spa, label: HTTPS, variant: emphasis }
- { from: spa, to: api, label: JSON }
- { from: api, to: idp, label: verify token, variant: security, labelAt: [531, 110] }
- { from: api, to: idp, label: verify token, variant: security, labelAt: [531, 110], decisions: [external-identity-provider] }
- { from: api, to: cache, label: read }
- { from: api, to: db, label: SQL }
- { from: api, to: storage, label: files }
@@ -0,0 +1,17 @@
schema_version: 1
decision:
id: external-identity-provider
title: Sign in through an identity provider, never store passwords
status: accepted
date: 2026-09-10
elements: [idp, api, spa]
diagrams: [0200-containers/web-app.archify]
context: >
The first version kept password hashes in the database and
had to handle resets, lockouts and two-factor codes itself.
decision: >
Users sign in through an OpenID Connect identity provider.
The single-page app gets a token; the API only verifies it.
consequences: >
No password tables, no reset flows. The identity provider
is a hard dependency: when it is down, nobody can sign in.