Skip to content

proposal: a step's failure is part of the model — retry, onError and {error}, on a delegate and on a send - #71

Open
iliyan-velichkov wants to merge 1 commit into
IntentFile:mainfrom
iliyan-velichkov:proposal/step-failure-resilience
Open

proposal: a step's failure is part of the model — retry, onError and {error}, on a delegate and on a send#71
iliyan-velichkov wants to merge 1 commit into
IntentFile:mainfrom
iliyan-velichkov:proposal/step-failure-resilience

Conversation

@iliyan-velichkov

Copy link
Copy Markdown
Contributor

Adds proposals/0033-step-failure-is-part-of-the-model.md. No versions/ change.

Why

A process step that calls out — provision a schema, register a client with an identity provider, ask a partner API, send a document — fails sometimes, and the format has nothing to say about it. Everything else about a flow is modelled: which step follows which, how a decision branches, what a boundary timer does when a task is not worked in time. The one thing left entirely to the platform's defaults is what happens when the work of a step does not succeed — so the model describes the application fully right up to the first failure, and then stops.

The send is the sharpest case, because the version document already prescribes the failure. It says of a delivery failure that "a sending process step SHOULD [fail the activity], since the message is that step's whole purpose" — which is right — and then offers no way to handle it. A failed activity is handled however the engine handles one: some bounded number of automatic attempts, then an incident against the job. The record the process is about is untouched. So a flow ordered the way it reads most naturally (issue → notify → mark sent) leaves a document that was issued correctly sitting in Issued for ever when a mail server is briefly unreachable, with no error message and no failure status. It is not visibly wrong; it is invisibly stalled.

The available workaround is to make the send the last step, so the worst case is an incident about the message alone. It works, and it constrains process design for a reason that has nothing to do with the domain: the end of a process becomes the only safe place to put a declared send.

What it specifies

retry: { count, every }, onError: <step | end> and the whole-value {error} placeholder, on the two service-task shapes whose work is such a call: delegate: and non-fan-out notify:.

The refusals are as much of the proposal as the keys, because a key that is accepted and inert is worse than one that is refused:

  • a setField / setRelationField step — a status write is refused by the model's own gates, and a gated one is refused to the person who acted, in the same interaction; routing that failure away would take the refusal out of their hands, and re-attempting a deterministic refusal recovers nothing;
  • a call: step, or a service task carrying neither shape;
  • a fan-out send (a notify with forEach) — the version already says a fan-out "MUST NOT fail its activity, because a retry would resend", so the step never fails and neither key could ever fire; those deliveries are observed per row with the notify block's outcome: field and the onNotifyFailed axis;
  • any step kind other than serviceTask.

One rule is worth singling out: the message made readable as {error} must name the cause, not just the step. It is the only account of the failure the record will carry, and "the message could not be sent" tells an operator nothing the status did not.

Declared step data (vars: + produces:/uses:/clearAfter) shipped alongside this in the reference implementation and is also unspecified here — deliberately out of scope, as a construct of its own.

Implementation

Proven out in the reference implementation: eclipse-dirigible/dirigible#7056 (the send half) and #6762 (the delegate half, which shipped earlier and was never proposed here). Covered there at the parser, emission, real-engine and end-to-end layers, including the case that decides whether the widening is safe at all: a handler raising its own business error still reaches its own boundary rather than being retried or re-coded.

…{error}, on a delegate and on a send

A process step that calls out fails sometimes, and the format has nothing to say
about it: everything else about the flow is modelled, and what happens when the
work of a step does not succeed is left entirely to the platform's defaults.

The send is the sharpest case, because the format already prescribes the failure
- it says a sending process step SHOULD fail the activity, since the message is
that step's whole purpose - and then offers nothing to handle it. So a flow with
the send ordered where it reads most naturally leaves a document that was issued
correctly sitting in Issued for ever when a mail server is briefly unreachable,
with no error message and no failure status, because the failure was recorded
against the job. The workaround is to make the send the last step, which
constrains process design for a reason unrelated to the domain.

Specifies retry: { count, every }, onError: <step | end> and the {error}
placeholder on the two service-task shapes whose work is such a call, with the
refusals spelled out: a setter step (a gated status write is refused to the
person who acted), a call: or bare step, a fan-out send (fail-soft per row, so
neither key could ever fire - its deliveries are observed with outcome: and
onNotifyFailed), and any other step kind.
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