Files
strangler-fig-demo/docs/adr/ADR-002-write-through-has-no-business-rules.md
T
ehoandClaude Sonnet 5 a6a1abbe9c feat: implement strangler-fig-demo Session 1 (backend + smoke script)
Builds the four-seam, three-write-path reference demo backend: case-framework
(seam D stand-in), legacy-backend/frontend (SQL Server, seams A/B/C targets),
and new-backend (Domain/Application/Infrastructure.*/Api implementing the
source resolver, take/release-ownership, write-through translator, and owned
assessment flow), wired together via docker-compose with a plain placeholder
frontend standing in for the Angular portal until Session 2.

All 11 Architecture.Tests pass and scripts/smoke.sh passes end-to-end against
a fresh `docker compose up`, covering acceptance criteria 1-3 and 7-22.

Fixes two real domain bugs found only once the stack ran for real: the BSN
eleven-proof checksum trivially passes all-zero digits, and the adoption
mapper silently treated a partial legacy address as absent instead of failing
loudly. Also fixes several environment-specific integration issues (rootless
Podman/SELinux bind-mount permissions, a buildah NuGet layer-caching bug,
SqlClient's invariant-globalization incompatibility, and an nginx path-prefix
mismatch for the legacy frontend).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 07:57:26 +02:00

2.6 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 (§7.5).

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:

  1. Maps the portal's 9-field request onto legacy's expected shape.
  2. Calls PUT legacy-backend/api/aanvragen/{id}/gegevens.
  3. 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 (§7.5), 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 systeem notice on the write-through form (§8.3) 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.Api type both constructs a legacy request DTO and touches a DbContext) is only a partial, structural proxy for this constraint — and is already close to vacuous given rule 3 (legacy DTOs are internal to New.Infrastructure.Legacy with no InternalsVisibleTo grant, so New.Api cannot 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.