From a37dfd47a4cdf41f610484b9b6f30a16a5204c95 Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Fri, 24 Jul 2026 15:21:28 +0200 Subject: [PATCH] ci: cap storybook-a11y resources + document the ACL learnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test-storybook:ci gets --maxWorkers=2 so the Jest runner stops spawning one headless Chromium per core and OOM-ing the Gitea runner host (the root cause). - storybook-a11y job gains a container resource ceiling (--cpus=2 --memory=4g) as a belt-and-suspenders guardrail; noted it needs a docker-mode act_runner. - openzaak-integration.md: add "Anti-corruption layer — two nested boundaries" teaching section (BFF ACL vs upstreams + FE ACL vs BFF, and the principles). Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 8 ++++++ docs/reference/openzaak-integration.md | 36 ++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f4dec1..110d529 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,14 @@ jobs: storybook-a11y: # Axe runs against every story in the static build; a violation fails the build. runs-on: ubuntu-latest + # Hard resource ceiling so a runaway test-storybook (one headless Chromium per Jest + # worker) can't OOM the runner host — it fails its own container instead. The real cap + # is `--maxWorkers=2` in test-storybook:ci; this is the belt-and-suspenders guardrail. + # NB: requires the Gitea act_runner to allow container jobs (docker mode). If the runner + # is host-only, drop this `container:` block and rely on the worker cap alone. + container: + image: node:24-bookworm + options: --cpus=2 --memory=4g --memory-swap=4g timeout-minutes: 15 steps: - uses: actions/checkout@v4 diff --git a/docs/reference/openzaak-integration.md b/docs/reference/openzaak-integration.md index 1750864..f69c9a8 100644 --- a/docs/reference/openzaak-integration.md +++ b/docs/reference/openzaak-integration.md @@ -79,6 +79,42 @@ path async if OpenZaak becomes the default. } ``` +## Anti-corruption layer — two nested boundaries (what to learn) + +This setup is an anti-corruption layer (ACL) **twice over**, and seeing them as a pair is the +lesson worth taking away: + +1. **The BFF guards everything against upstream systems.** OpenZaak's foreign model — + URL-as-identity, a `zaaktype` that is a URL _into another service_, `{count,next,previous, +results}` pagination, HS256 JWT auth — never leaves the BFF. `ZgwZaakMapper` translates it + into the BFF's own `ApplicationSummaryDto`; `IZaakSource` makes the boundary swappable + (`LocalZaakSource` vs `OpenZaakZaakSource` return the _same_ DTO). +2. **The Angular app guards itself against the BFF.** `infrastructure/` is the only layer that + touches the network (lint-enforced); every response crosses a `parse*` (`Result`) trust + boundary + a `toDomain` mapper before any domain/UI code sees it (ADR-0001, ARCHITECTURE §6). + +The DTO at `/api/v1` is the membrane between them — which is why wiring OpenZaak touched **zero +frontend code and produced zero api-client drift**. That was the proof the ACL held. + +Principles this demonstrates: + +- **An ACL is a _mapping_, not a passthrough.** A DTO that is the upstream shape renamed is + corruption with extra steps; the valuable ACLs here (`ZgwZaakMapper`, the `parse*`/`toDomain` + pairs) actively translate a foreign model into a local one. +- **Put the ACL where trust changes, and make it the _only_ place.** One choke point per + boundary — the `Zgw/` folder + `IZaakSource` server-side, `infrastructure/` client-side. +- **Decision DTOs and the ACL are complementary.** BFF-lite (server decides, FE renders) is an + ACL against _business-rule_ drift, layered on the ACL against _data-shape_ drift. +- **A real seam is swap-testable offline.** Because the ACL returns a stable DTO, the ZGW + client is unit-testable with fixtures + a stub handler — no live OpenZaak. +- **Mark the honest edges.** The `ponytail:` sync-over-async note and the "coarse status map" + comment in `ZgwZaakMapper` show where the ACL is deliberately thin — an ACL need not be + complete on day one, but its shortcuts should be visible. + +Caveat: today only the cases **read** path has a source interface (`IZaakSource`). Other BFF +endpoints still read `SeedData`/static stores directly — ACL-ready (the DTO seam exists) but not +yet swappable. That is the WP-50/51/52 roadmap. + ## See also - [ADR-0005 — OpenZaak behind the BFF](architecture/0005-openzaak-behind-bff.md) — the decision. diff --git a/package.json b/package.json index f195cae..1a9386d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "storybook": "ng run atomic-design-poc:storybook", "build-storybook": "ng run atomic-design-poc:build-storybook", "test-storybook": "test-storybook", - "test-storybook:ci": "concurrently -k -s first -n sb,axe \"http-server storybook-static -p 6006 --silent\" \"wait-on tcp:127.0.0.1:6006 && test-storybook --url http://127.0.0.1:6006\"", + "test-storybook:ci": "concurrently -k -s first -n sb,axe \"http-server storybook-static -p 6006 --silent\" \"wait-on tcp:127.0.0.1:6006 && test-storybook --url http://127.0.0.1:6006 --maxWorkers=2\"", "check:tokens": "bash scripts/check-tokens.sh", "dep:check": "depcruise src/app --config .dependency-cruiser.js", "dep:graph": "bash scripts/dep-graph.sh",