All checks were successful
## What & why Second sub-slice of **S-11 · Withdrawal (Flow 3)** (#12). S-11a (#88) made a withdrawal advance the aggregate to INGETROKKEN; this sub-slice **cancels the running Flowable process** so the withdrawn case leaves the behandelaar's werkbak. - **BPMN** (`registratie.bpmn`): an interrupting message boundary event (`RegistratieIngetrokken`) on the `Beoordelen` task, routing to a dedicated "Registratie ingetrokken" end event. - **Workflow Client**: `WithdrawBeoordelingAsync(executionId)` delivers `messageEventReceived` to the task's execution (PUT); `BeoordelingTask` now carries its `executionId`. - **`WithdrawRegistration` handler**: after the domain transition, finds the open `Beoordelen` task for the registration and delivers the withdrawal message — best-effort, mirroring how the beoordeling completes its task. - **Werkbak**: also filters out registrations that are no longer open, so a withdrawn case never surfaces even in the brief window before cancellation lands. - **ADR-0014** records the decision (message event in BPMN vs. deleting the instance from code). - **verify (`run-domain-check.sh`)**: a second registration parks at `Beoordelen`, is withdrawn via the domain, and the check asserts its `Beoordelen` task disappears — so verify-stack validates the live Flowable message correlation. Refs #12 (S-11c — the BFF + self-service "trek aanvraag in" button + e2e — closes it). ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing tests committed before the implementation (red → green per commit). - [x] Implementation makes the tests pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected (BPMN redeploys on a fresh CI DB via flowable-init). - [x] ADR added (ADR-0014). - [x] Docs — the user-visible demo note lands with S-11c. ## Notes for reviewers - Verified locally: `Big.Tests` 94/94 pass; `Big.Api` builds; `registratie.bpmn` is well-formed. - The Flowable message-correlation REST shape is validated **live** by verify-stack (the Workflow Client unit tests stub the exchange and assert only the request shape, per ADR-0009) — the new `run-domain-check.sh` withdrawal step is that live check. - Known gap (ADR-0014): a withdrawal that races ahead of the process reaching `Beoordelen` finds no task to cancel; the aggregate is still INGETROKKEN and the werkbak filter hides it, but that instance parks unattended. A process-level event subprocess would close the gap — deferred. Reviewed-on: #89
4.3 KiB
4.3 KiB
ADR-0014: Withdrawal cancels the registratie process via a BPMN message event
- Status: Accepted
- Date: 2026-07-16
- Deciders: Respellion engineering
- Relates to: S-11 (#12); builds on ADR-0009 (external-task worker / Workflow Client), ADR-0013 (behandel-portal wiring, the Beoordelen user task)
Context
S-11 lets a zorgprofessional withdraw a still-open registration ("trek aanvraag in"). S-11a already
advances the aggregate to INGETROKKEN (domain state). But the registratie process is still running in
Flowable — parked at the Beoordelen user task — so without a second step the withdrawn registration
would linger as work for a behandelaar. The withdrawal must also cancel the running process.
Two questions shape this sub-slice.
- How does the case get cancelled — in code, or in the BPMN model?
- How does a withdrawal correlate to the right running process instance?
Decision
The BPMN models the cancellation as an interrupting message boundary event on the Beoordelen
task; the Workflow Client correlates a RegistratieIngetrokken message to the task's execution.
- Modelled in BPMN, not deleted from code. The
Beoordelenuser task carries an interrupting message boundary event (RegistratieIngetrokken) that routes to a dedicated "Registratie ingetrokken" end event. The process's own model says how a withdrawal ends it — the Workflow Client only delivers the message; it never reaches into Flowable to delete an instance. This keeps the workflow's control flow in the workflow (§8.2) and leaves an audit trail in Flowable history (the process ended via the ingetrokken path, not a raw delete). - Correlated by the registration's own process instance. The aggregate records its Flowable
process instance id at submit, so the
WithdrawRegistrationhandler correlates directly by that id — no task lookup. The Workflow Client asks Flowable for the execution subscribed to theRegistratieIngetrokkenmessage in that instance and deliversmessageEventReceivedto it. Targeting the subscribed execution (not the user task's execution — a message boundary event's subscription lives on its own execution) is what makes the correlation land. - Best-effort, mirroring the beoordeling. If no open
Beoordelentask is found (the process has not yet parked there — theOpenZaakAanmakenwindow — or has already ended), the withdrawal still stands: the aggregate is INGETROKKEN and the werkbak filters it out regardless (S-11b). We complete the domain transition first and cancel the workflow best-effort, exactly asBeoordeelRegistratiecompletes its task best-effort.
Consequences
Positive
- The cancellation path is visible in
registratie.bpmn; the Workflow Client stays the only code that talks to Flowable and does not delete instances behind the model's back. - Reuses the existing task-query correlation — no new plumbing, no correlation store.
- A withdrawn case leaves the werkbak (its
Beoordelentask is cancelled), and the werkbak also filters non-open registrations as a belt-and-braces for the brief window before cancellation lands.
Negative / costs
- A withdrawal raced ahead of the process reaching
Beoordelen(duringOpenZaakAanmaken, seconds) finds no task to cancel, so that process instance runs on toBeoordelenand parks there with no one to act on it (it is hidden from the werkbak by the status filter). Acceptable for this reference at these volumes; a process-level interrupting event subprocess would close the gap and is an additive follow-up if it matters. - The Flowable message-correlation REST shape is validated live (verify-stack), not in the Workflow Client's unit tests, which stub the HTTP exchange and assert only the request shape (consistent with ADR-0009).
Alternatives considered
- Delete the process instance from the Workflow Client (
DELETE /runtime/process-instances/{id}) — rejected: it cancels the case but hides the reason from the BPMN model; the "why" lives in code, not the process. The message event keeps the cancellation a first-class part of the workflow. - Interrupting message event subprocess at process level — more robust (correlates anytime,
closing the
OpenZaakAanmaken-race gap), but a heavier BPMN construct; deferred as an additive change if the race proves to matter.