Files
ehoandClaude Haiku 4.5 ddec15ccb2 docs(architecture): add diagrams and implementation playbook
Added three new documents with nine Mermaid diagrams to make the strangler
fig strategy visible:

- README: container topology diagram at the start, with the proxy entry point
  and three seams labelled
- docs/architecture.md: five diagrams tracing the exact implementation:
  - The four seams and who holds authority at each boundary
  - How by-id read goes through the resolver, but list-read bypasses it
  - Case lifecycle state machine (the strategy in one picture)
  - Take-ownership sequence with failure windows annotated
  - Write-through error round-trip showing zero validation logic crossed
- docs/playbook.md: how to apply this to a production system:
  - Write-path decision tree (five read/write patterns)
  - Cutover ordering diagram (side-effects-free first, least recoverable last)
  - Seven transferable rules with pointers to the files that demonstrate them
  - Scope diagram of what's proven vs. left as your decisions

Resolved all 13 dangling § citations (to an absent spec doc) by linking to
the actual files or dropping them. Replaced portal-frontend/README.md
boilerplate with accurate content. All diagrams parse and link-check clean.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-01 09:15:39 +02:00

96 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Applying this to a production system
The demo shows *that* the seams hold. This page is the transferable part: how
to pick a path for each capability, and the order that keeps a cutover cheap
to get wrong.
## Which write path for which capability
Ask this per capability, not per system. Most systems end up running all five
answers at once — that is the point of the pattern, not a sign of a messy
migration.
```mermaid
flowchart TD
S(["Pick one capability<br/>in the legacy system"]) --> Q1
Q1{"Does the new UI<br/>only need to read it?"}
Q1 -->|yes| A["<b>Read ACL</b><br/>translate at the boundary<br/>legacy stays authoritative"]
Q1 -->|no| Q2{"Is it owned by a system<br/>you don't control?"}
Q2 -->|yes| D["<b>Conformist</b><br/>surface its rules as-is<br/>don't fight or hide them"]
Q2 -->|no| Q3{"Have you rebuilt the<br/>domain rules yet?"}
Q3 -->|yes| E["<b>Take ownership</b><br/>new system becomes<br/>the authority"]
Q3 -->|"no — and it's<br/>a whole workflow"| C["<b>Redirect</b><br/>send the user back out<br/>to the legacy screen"]
Q3 -->|"no — but it's<br/>a simple edit"| B["<b>Write-through</b><br/>legacy still validates<br/>translator gets zero rules"]
```
Read ACL and write-through are the cheap ones and where most capabilities
should sit for most of the migration. Take-ownership is the only path that
moves authority, so it is the only one that needs a rollback story.
## Ordering a cutover so failures stay cheap
The generalisable rule from `TakeOwnershipHandler`: **free checks first,
external systems before your local transaction, least-recoverable action
last** — and for whatever remains unrecoverable, write down how you would
*detect* it instead of pretending it rolls back.
```mermaid
flowchart LR
subgraph free["Costs nothing to fail"]
direction TB
S1["1 · cheap guard<br/>already migrated?"] --> S2["2 · read the source"] --> S3["3 · map it<br/>invariants run here"]
end
subgraph writes["Each failure leaves a trace"]
direction TB
S4["4 · external system"] --> S5["5 · your local tx<br/>atomic"] --> S6["6 · flip the old flag"]
end
free ==>|"a failure up to here<br/>means nothing happened"| writes
S4 -.->|"fails after?"| F1["orphaned external record<br/><i>find by external reference</i>"]
S6 -.->|"fails?"| F2["split-brain<br/><i>reconcile the two flags</i>"]
```
Steps 13 doubling as a dry-run endpoint is what makes the rehearsal
trustworthy: it *is* the real cutover's own check code, so it cannot drift
away from what the real call will do.
## Seven rules worth stealing
| # | Rule | Why | Demonstrated by |
|---|---|---|---|
| 1 | Migrate the smallest unit that already exists in the domain | Here it's one case. Per-unit cutover means a failure is one bad row, not a bad weekend | [ADR-003](adr/ADR-003-ownership-is-taken-per-case.md) |
| 2 | The translator carries **no** business rules | The urge to "just check the postcode here too" is the signal to migrate that capability instead | [ADR-002](adr/ADR-002-write-through-has-no-business-rules.md) |
| 3 | Give yourself a dry run built from the real thing | A rehearsal that shares code with the performance cannot go stale | `GET .../take-ownership/preflight` |
| 4 | Let the old system keep saying no | Every legacy error surfaces verbatim; none is invented or hidden | `LegacyDetailsWriteThroughTranslator` |
| 5 | Make the boundary fail the build | Exactly one type may know both sources exist; a reviewer will eventually miss that, a test won't | `tests/Architecture.Tests` (11 rules) |
| 6 | Write down what you deliberately did **not** build | An omission you named is a decision; an omission you didn't is a bug waiting | [sync-not-implemented.md](sync-not-implemented.md) |
| 7 | Reversibility expires — say so out loud | Release works until the new side holds authoritative writes; after that the honest answer is a `409`, not a silent discard | `ReleaseOwnershipHandler` |
## What this demo deliberately leaves for you
None of these are hard to add; all of them are decisions a real migration has
to make explicitly, so the demo declines to make them for you.
```mermaid
flowchart TB
subgraph shown["Proven here"]
direction LR
P1["4 seams"] ~~~ P2["3 write paths"] ~~~ P3["per-case cutover<br/>+ dry run"] ~~~ P4["reversal, while<br/>still reversible"]
end
subgraph yours["Yours to decide"]
direction LR
Y1["bulk / scheduled<br/>cutover"] ~~~ Y2["new → old sync"] ~~~ Y3["authn / authz"] ~~~ Y4["metrics, alerting,<br/>reconciliation jobs"]
end
shown --> yours
```
Two of these are already argued in writing rather than left blank:
[ADR-003](adr/ADR-003-ownership-is-taken-per-case.md) on why bulk migration is
a separate later capability, and
[sync-not-implemented.md](sync-not-implemented.md) on the two visible
consequences of having no sync.