The workflow definition
Table of Contents
A workflow definition declares the step sequence of one workflow type. A definition is code, not data: the component that owns a workflow's steps builds the definition, and the workflow service registers it at startup in an in-process registry keyed by type name. A start message names the type, the engine resolves it in the registry, and the definition builds the step list for that run once, at start, after which the resolved list is persisted on the run. This page defines the parts of a definition, how one is added or changed, and what a change cannot affect.
1. Detail
1.1. The parts of a definition
A definition carries a type name, a description, a failure policy, and one function that builds the step list for a run. The type name is the interface: it is the key in the registry, the value the start message carries, and the value the run stores. The failure policy is the definition's choice between compensating a failed run and stopping it for a retry, and Failure, compensation and retry defines both.
Figure 1: The workflow definition and its step definitions: the definition builds a step list once per run, and the resolved steps are persisted on the run.
Source: https://github.com/OreStudio/OreStudio/blob/main/doc/knowledge/architecture/workflow_definition.puml.
Regenerate with
plantuml -tpng doc/knowledge/architecture/workflow_definition.puml.
A step definition carries a name, a description, the subject that answers its command, an optional subject that answers its compensation, the deadline it must answer inside, a function that builds the command payload, and an optional function that builds the compensation payload. A step is addressed by its name, so a later step reads the result of an earlier one by name rather than by position.
The deadline is required, and the engine refuses a run whose definition built a
step without one: a step that states none is a command the engine would wait on
for ever, because nothing else in the dispatch path can tell a service that has
died from one that is slow. Definitions read their budgets from
data_step_timeout and write_step_timeout, which the definition contract in
ores.workflow.api states, and a step whose work is neither states its own.
Failure, compensation and retry
states how the engine enforces it and what a run does when it expires.
1.2. Definitions are code
A definition lives in the component that owns the steps, beside the handlers that answer the step subjects, and the workflow service registers it at startup. The reason is coupling: a step's command subject and its payload builder must agree with the handler that answers the subject, so the definition and the handler change together. Holding the definition outside the component that implements it would split one change into two.
The registry is in-process and keyed by type name; registering a type name that already exists replaces the entry. The store holds runs and steps, not definitions: there is no definition table, no definition write operation, and no version row for a definition.
1.3. Adding a workflow
Four changes add a workflow, and they belong to two owners.
- The owning component builds the definition: a function that returns the step list for a run, and the payload builders each step needs.
- The owning component implements the handlers that answer the step command subjects.
- The workflow service registers the definition in its registrar, beside the definitions of the other components. Every step it builds must state a deadline, and the engine refuses a run whose definition states none, so a new step kind states its budget where it states its words.
- The caller publishes a start message that names the new type.
A start message that names a type the registry does not hold is refused at start, so an unregistered definition is a refusal to the caller rather than a run that stalls.
1.4. Changing a workflow
A change to a definition is a code change and a deployment. There is no update message and no stored version: the deployed build is the version of the definition.
Two rules make the change safe.
- Keep the type name stable. A start message names the type, and a run stores it. A rename therefore breaks every caller and every existing run, and a new type name is a new workflow rather than a new version of an old one.
- Keep answering the old step subjects until their runs have ended. The step subjects are contracts with handlers. A deployment that stops answering a subject leaves the runs that started under the old definition without an answer, because the engine dispatches the persisted step list and does not consult the new definition for them.
Figure 2: Registering a definition at startup, and resolving it when a start message arrives.
Source: https://github.com/OreStudio/OreStudio/blob/main/doc/knowledge/architecture/workflow_definition_registration.puml.
Regenerate with
plantuml -tpng doc/knowledge/architecture/workflow_definition_registration.puml.
1.5. What protects a run in flight
The engine builds the step list once, at start, and persists the result on the run: the step count, and the name, command subject and compensation subject of each step. The definition is not consulted again for that run.
This is what makes a definition change safe for work in progress. A restart does not rebuild the list, and neither does a deployment that changes the definition. A run that started under one step sequence finishes under that sequence, and a run that starts after the deployment uses the new one. Reshaping work in progress is not possible by deployment, which removes the class of failure where a step is removed or reordered under a run that is waiting on it.
1.6. What the definition does not hold
The definition declares the shape of a run, and the run's parameters are data. The start message carries a request payload, and the definition's build function reads that payload, the tenant and the correlation identifier to decide which steps apply and what each command carries.
The separation matters when a user chooses a starting point. The choice is registered data with its own write path, and the step sequence is code. The tenant provisioning workflow is the worked case: the profile a user selects is data, and the step kinds the profile selects are code. See Provisioning operations and the plan.
2. See also
- Workflows — the structure note that orders this cluster, and where to read this page in it.
- Process Manager — the pattern the engine implements, and the reason a definition declares steps rather than performing them.