Skip to content

fix: reverse a declined alternative without cancelling a missing order - #9

Merged
emaarco merged 2 commits into
mainfrom
fix/declined-alternative-incident
Oct 8, 2026
Merged

emaarco merged 2 commits into
mainfrom
fix/declined-alternative-incident

Conversation

@emaarco

@emaarco emaarco commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Why

When the requested bike was out of stock, serviceTask_orderBike completed normally with bikeAvailable = false and no orderId. BPMN registers compensation for every completed activity, so when the back office then declined an alternative (alternativeFound = false) the order compensation callActivity_cancelBikeOrder ran with a missing orderId, the cancellation task failed and the instance ended in an incident; endEvent_contractCancelled was never reached. The same happened when the customer withdrew while userTask_clarifyAlternative was open. On top of that, nothing set the application to CANCELLED on the declined path.

Port of miragon-blueprints/cibseven-remote-example#31 (twin: miragon-blueprints/cibseven-process-engine-api-example#24). Issues are disabled in this repository, so there is no issue to close.

What

  • Out-of-stock is a BPMN error on orderBike. New error Error_BikeUnavailable (bikeUnavailable) with an interrupting boundary event event_bikeUnavailable on serviceTask_orderBike leading to userTask_clarifyAlternative. gateway_bikeAvailable and the bikeAvailable variable are gone; orderBike flows straight to the parallel join. OrderBikeUseCase returns the OrderId and throws the new domain exception BikeUnavailableException; OrderBikeWorker translates it into BpmnErrorOccurred with the generated Errors.BIKE_UNAVAILABLE code. A task left through an error registers no compensation.
  • The declined path reaches a terminal status. New service task serviceTask_confirmContractCancellation between event_triggerReversal and endEvent_contractCancelled, reusing the existing sendCancellationConfirmation topic. No new code.
  • validateApplication is no longer a process step. The task, its error boundary event, Error_ApplicationInvalid, the worker, use case, service and ApplicationInvalidException are removed. The income check moved into LeasingApplication.receive, which throws IllegalArgumentException; the existing GlobalExceptionConfiguration answers it with a 400 problem detail.
  • Diagram laid out with the bike order on the main axis; docs/assets/bike-leasing.png re-exported.
  • Tests: process tests adapted, plus three new ones (declined alternative; withdrawal while clarifying; withdrawal after an accepted alternative cancels the one order exactly once). BikeUnavailableTransactionTest runs the out-of-stock order with the real services.
  • Bruno: new scenarios 08-alternative-declined and 09-invalid-request.
  • Typed process API regenerated with ./mvnw -pl service/app generate-sources.

Behaviour change to be aware of

  • A leasing request with monthlyNetIncome <= 0 is now refused synchronously with 400 Invalid request and starts no process. Before, it was accepted with 200 and rejected by the process afterwards.
  • The process variable bikeAvailable no longer exists; the model changed, so running instances of the old definition keep the old shape.
  • @Transactional(noRollbackFor = ...) was deliberately not added to OrderBikeService: BikeUnavailableTransactionTest is green without it, because the @ProcessEngineWorker is not transactional and the service's rolled-back transaction does not enclose the engine call that reports the BPMN error.

How it was verified

  • ./mvnw verify green locally (architecture, Checkstyle, unit, process, model validation, OpenAPI export; openapi/openapi.json unchanged).
  • Diff-scoped PIT run locally on the changed classes: 13 of 14 mutations killed (93 %).
  • npm run lint:bpmn clean.
  • Exported PNG inspected: no overlaps.
  • The Bruno scenarios were not run locally; the end-to-end job of this PR runs them.

An out-of-stock bike completed serviceTask_orderBike normally, so BPMN registered its
compensation. Declining the alternative (or withdrawing while it was being clarified) then ran
callActivity_cancelBikeOrder without an orderId and ended in an incident.

- orderBike raises the BPMN error bikeUnavailable, caught by an interrupting boundary event that
  leads to the clarification task; gateway_bikeAvailable and the bikeAvailable variable are gone
- the declined path sends the cancellation confirmation, so the application reaches CANCELLED
- validateApplication is no longer a process step: a request without income is refused with a
  400 when the application is received

Refs miragon-blueprints/cibseven-remote-example#31
@emaarco
emaarco merged commit 1d1f760 into main Oct 8, 2026
4 checks passed
@emaarco
emaarco deleted the fix/declined-alternative-incident branch October 8, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant