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>
2.7 KiB
ADR-002: The write-through translator carries no business rules
Status
Accepted.
Context
Seam B lets a user edit a legacy-owned case's applicant details (name, address, contact) from the new portal, without the new system taking ownership of that case. The legacy system remains the authority on this data until ownership is explicitly taken (ADR-003).
It is tempting, once a translation layer exists between the portal's request
shape and legacy's PUT /api/aanvragen/{id}/gegevens shape, to also smuggle
in a validation shortcut or two — "just check the postcode format here too, it
saves a round trip." That temptation is exactly what this ADR forecloses.
Decision
New.Infrastructure.Legacy's write-through translator (the type backing
ILegacyCaseGateway.UpdateDetailsAsync) contains no business rules: no
conditionals on request values, no validation beyond null/shape checks, no
derived values, no defaulting. It only:
- Maps the portal's 9-field request onto legacy's expected shape.
- Calls
PUT legacy-backend/api/aanvragen/{id}/gegevens. - Maps legacy's response — success, or every returned field error via the
veld/code→ portal-field-path table — back into the portal's error shape, including a generic fallback for any unrecognized legacy code (logged as a warning, never dropped or guessed at).
If a rule needs to be enforced on this data from the new portal, that is a signal the capability should be taken into ownership instead, not patched into the translator.
Consequences
- The portal cannot offer a better validation experience than legacy already
has for this seam — by design. The
Gevalideerd door het legacy systeemnotice on the write-through form (portal-frontend/src/app/case-detail/edit-applicant-details/) exists specifically so the user knows why: this is the honest version of a seamless UI, not a limitation to hide. - Rule 11 in Architecture.Tests (no
New.Apitype both constructs a legacy request DTO and touches aDbContext) is only a partial, structural proxy for this constraint — and is already close to vacuous given rule 3 (legacy DTOs areinternaltoNew.Infrastructure.Legacywith noInternalsVisibleTogrant, soNew.Apicannot even name them). The stronger claim this ADR makes — that the translator itself contains no conditional business logic — is a code-review rule, not a machine-enforced one. We say so here rather than implying test coverage that does not exist. - Any future temptation to "just add one small check" in the translator should instead be read as a signal to take that capability into ownership.