Files
register-referentie/docs/architecture/adr-0032-werkbak-live-refresh.md
T
not 0fe4813388 docs(portals): ADR-0034 — Caddy serves the portals (refs #166)
Records the decision, the directive-order footgun that shapes the Caddyfiles, and
the measured cost (the images grew 75.7 MB → 90.6 MB). Also updates the three
frontend-decisions entries and the two other docs that named nginx.
2026-09-04 17:25:29 +02:00

3.9 KiB

ADR-0032: The werkbak refreshes itself by polling, not by a pushed stream

  • Status: Accepted
  • Date: 2026-09-04
  • Deciders: Respellion engineering
  • Slice: #162 (proposal #163). The issue titles it S-26; that id already belongs to the self-service resume slice (#111), so #162 is the identifier that counts.

Context

The werkbak (S-12) is a read of the open Flowable Beoordelen tasks: portal → BFF GET /behandel/werkbak → domain Werkbak query → workflow engine, each task enriched from its aggregate. A registration reaches Beoordelen asynchronously, only once the citizen supplies its documents and the DMN routes it (S-10a) — so it appears in a werkbak that is already open, and until now a behandelaar had to reload the page to see it.

Three forces shape the mechanism:

  • Nothing notifies anyone. The trigger lives in Flowable. The domain does not publish task events, and there is no bus between the domain and the BFF.
  • The BFF is stateless and sits behind each portal's reverse proxy.
  • This is the repo's first live-updating view, so the choice sets a precedent.

Decision

The werkbak page re-reads the existing BFF endpoint on a fixed interval (WERKBAK_REFRESH_MS, 5 s) while it is open. No new endpoint, dependency or server-side state.

The refresh is a background read: it leaves the rows and the loading/failure states untouched until it has an answer, so a tick never flashes a spinner over rows a behandelaar is reading and a single failed poll never swaps the list for the error alert. A read that comes back also clears an earlier failure, so the view recovers on its own — the same reload this slice set out to remove would otherwise be needed to escape a transient error. Only a foreground read (on open, after a decision) speaks for whether the werkbak is readable at all.

Why not SSE or WebSockets

Neither buys freshness here, because nothing notifies the BFF either:

  • SSE (text/event-stream) would mean a new streaming endpoint whose handler polls the domain and forwards diffs — the same latency, plus connection lifecycle, proxy buffering, and auth on a long-lived connection.
  • WebSocket/SignalR adds a dependency (CLAUDE.md §13) and makes the BFF stateful and sticky-session-bound. A genuine push path would also need the domain to publish task events. Warranted by high-frequency, bidirectional or fan-out-heavy traffic; the werkbak is none of those.

Polling meets the acceptance ("a registration can be seen in the werkbak once it is ready for review") in a handful of lines inside one component.

  • ponytail ceiling: a fixed 5 s interval, per open page, that keeps polling in a background tab. Each tick costs one Flowable task query plus a store read per open task.
  • Upgrade path: publish task events from the domain, then swap the component's interval for a stream. The endpoint contract and the component's rendering stay as they are; gate on document.visibilityState first if request volume is the concern.

Consequences

Positive

  • The outcome is delivered with no new endpoint, dependency, or server-side state, and no service boundary moves.
  • Self-healing: a transient read failure no longer strands the view until a manual reload.
  • The e2e got simpler — the happy path waits for the werkbak row without reloading the page, which is itself the live-refresh assertion.

Negative / costs

  • Staleness is bounded by one interval (≤5 s) rather than instant.
  • One GET /behandel/werkbak per open werkbak per interval, including in hidden tabs.
  • The precedent is polling; a future view with genuinely high-frequency updates will have to revisit this (see the upgrade path above).

Coupling rules touched (CLAUDE.md §8)

None. The poll reuses the existing portal → BFF → domain read path: §8.3 (portals talk only to the BFF) and §8.2 (only the Workflow Client talks to Flowable) are unchanged.