Continuity
Some operations are sequences of steps that reach beyond the component: create an account, then register it with an external system, then send a welcome message. Written as an effect, the sequence reads naturally:
export async function effect (input: Input, context: Context) {
const account = await context.remote.accounts.create({ input })
await context.fetch('https://crm.example.com/contacts', {
method: 'POST',
body: JSON.stringify(account)
})
await context.remote.messages.send({ input: { to: account.email } })
} Run as a task or by a receiver, this effect is retried when it fails. A retry starts the function from the top. If sending the message failed, the next attempt creates the account again and posts to the external system again.
Transactional Inbox protects state inside the application. It cannot protect the external system, and it does not help an operation whose entire purpose is the sequence itself.
§Picking up where it failed
An effect can be declared continuous:
operations:
onboard:
type: effect
continuity:
onboard: 604800 For such an operation, the runtime keeps what the context answered at each step of a run. On a later attempt, the function still runs from the top, but a step that already answered is not performed again: the context returns the answer it gave before. The first step actually executed is the one that failed.
In the example, if send fails, the next attempt receives the same account from create and
the same response from the external system, without either being contacted. Only send is made
again.
The implementation contains nothing about this. It is written as a plain sequence, and it is the same code whether or not it is ever interrupted.
§What a step is
A step is an interaction made through the context: a call to another component, a request to an external system, an access to the shared cache. This is one of the reasons the context is the only door out of an operation. The runtime can give back only what passed through it.
A step is recognized by what it asks, not by when it was asked. Two attempts that make the same request are making the same step. For that to work, the requests have to be the same, which puts one requirement on the implementation: values that would differ between attempts come from the context.
// a different request on every attempt; made again every time
await context.remote.invoices.issue({ input: { at: Date.now() } })
// the same request on every attempt; made once
await context.remote.invoices.issue({ input: { at: context.now() } }) context.now(), context.random(), and context.id() are themselves steps. Their answers are
kept and given back, so a run sees one moment, one random value, and one new identifier
throughout, no matter how many attempts it takes.
§What can be counted on
What answered is not asked again, across failures, process restarts, and redeployments, for as long as the run can still be resumed.
The next attempt may run anywhere. Whichever replica is free takes it. What the context answered travels with the run; what the operation kept in its own process does not.
Calls keep their identity. A call made from a continuous effect has the same identity on
every attempt, so a callee that declares once recognizes a repetition even in the cases below.
§What cannot
A step can still happen twice. If the process stops after a step took effect and before its
answer was kept, the next attempt makes it again. Within the application, once closes this
gap. A request to an external system is sent again, and is safe only if that system can
recognize the repetition.
A run is resumable for a declared time. The number in the declaration is how long an unfinished run is remembered. After that, a retry starts from the beginning.
Not every answer can be kept. A step that answers with a stream has nothing that could be given back later.
§Where it applies
Continuity is for effects run where nobody waits: as tasks or by receivers. Those are the cases in which the runtime, not a caller, retries. An effect called directly fails to its caller, who decides what to do; there is no later attempt to resume.