All checks were successful
CI / lint (pull_request) Successful in 1m15s
CI / build (pull_request) Successful in 1m1s
CI / unit (pull_request) Successful in 1m20s
CI / frontend (pull_request) Successful in 2m50s
CI / mutation (pull_request) Successful in 5m22s
CI / verify-stack (pull_request) Successful in 7m18s
verify-stack surfaced a Flowable 500: delivering messageEventReceived to the Beoordelen task's execution is wrong — a message boundary event's subscription lives on its own execution. Correlate instead by the registration's process instance id (recorded at submit): query the execution subscribed to RegistratieIngetrokken and deliver the message there. Withdrawal moves from IUserTaskClient to IWorkflowClient.WithdrawProcessAsync; the handler no longer needs a task lookup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.