Tasks / Workflows
Reboot provides a mechanism to run asynchronous tasks. This is useful for doing things that do not fit in the synchronous request / response model, including:
- Operations that you want to run at some later scheduled time.
- Operations that perform a side-effect outside of your Reboot application.
- Operations that need to wait until a specific condition is met before executing.
- Business processes that take an arbitrarily long to complete.
Lifecycle
Tasks are created by scheduling or spawning a
call to one of your writer
,
transaction
, or
workflow
methods.
Tasks are designed to run to completion: if a failure occurs while executing a task, e.g., the machine dies, the task will be retried. A task will also retry if an unexpected exception is raised, but you can complete a task by aborting with a declared error.
Workflows are for side-effects and arbitrarily long operations
While you can create writer
and
transaction
tasks, Reboot expects
those methods to be side-effect free in order to guarantee safety in
the presence of failures and retries (during development we try and
enforce this by executing your methods twice, similar to "strict mode"
in React).
What about when you need to make a side-effect? For example, updating some external data store or calling into another service that mutates some state?
To accomplish this in Reboot you use a
workflow
method. Unlike a writer
or
transaction
which are atomic by default, a workflow
is not. For
this reason, a workflow
can only ever be run as a task, where it
is guaranteed to be retried. By retrying, the workflow is able to
eventually converge on some desired outcome.
Because a workflow
is not atomic, it does not block other writer
or transaction
methods from executing at the same time. This makes a
workflow
the right place to perform arbitrarily long operations that
might need to wait a long time until certain conditions are
satisified, or certain computations are completed.
Idempotent convergence vs a durable log
Developing workflows are different in Reboot than other frameworks
because Reboot does not use a durable log to track each of the "steps"
in the workflow. Instead in Reboot you use idempotency, a built-in
primitive of the framework, to only perform a "step" in your
workflow
once.
To make your workflow
's safe Reboot requires that every call within
a workflow
be made by explicitly specifying an idempotency key or alias,
which allows a developer to perform a call at most once, or every
time. Because a workflow
is executed as a task, and a task is
retried until completion, your calls will eventually get executed
exactly once.
In addition to making idempotent calls, Reboot provides the ability to execute blocks of code at least once or at most once, which is useful for calling outside of your Reboot application.
We call the Reboot approach idempotent convergence to differentiate
it from a durable log. Idempotent convergence has a bunch of
benefits, perhaps most notably, it makes it very easy for a developer
to make changes to their code freely because there is no expected
path of execution that corresponds to entries in the durable
log. Instead, a workflow
method's code may change dramatically from
one retry to another, e.g., to fix a bug, which lets the programmer do
what ever they need to do in order to focus on the end goal of
converging on some desired outcome.