A workflow is a finite-state machine that is defined in YAML format and follows specific rules and constraints. More precisely, a workflow consists of
- a non-empty unique
name
, - a non-empty finite set of named
states
, - a non-empty finite set of
transitions
correlating states by their names, and - optionally, a set of
groups
collating states by their names,
see the wfx OpenAPI Specification's Workflow
object for reference.
wfx supports dynamic loading and unloading of workflows at run-time; when a workflow is loaded into wfx, it is validated to ensure that it adheres to these rules and constraints. Upon loading a workflow into wfx it is persistently stored (see Configuration) and is henceforth immutable. However, as long as there is no job referencing it ― including finished ones ― workflows can be unloaded, i.e., deleted from wfx's persistent storage. After having corrected the unloaded workflow, it can be loaded into wfx again.
Graph-wise, the set of states defines the nodes of the finite-state machine graph and the set of transitions defines the directed edges among the nodes. In the optional set of groups, disjoint sets of states can be combined for semantic grouping and easier state query selection.
Each state in states
consists of
- a unique
name
, and - a
description
.
Each group in groups
consists of
- a unique
name
and - a non-empty set of
states
' names.
Each transition in transitions
consists of
- a starting state name
from
matching one of the unique state names instates
, - an ending state name
to
matching one of the unique state names instates
, - an
eligible
attribute denoting the entity that may execute the transition, eitherCLIENT
orWFX
, and - an optional
action
attribute that ― depending on theeligible
entity ― specifies the transition execution action.
Note: Trivial transitions, where the source and destination states are the same (from == to
), are implicit in the workflow.
These transitions allow the client to report progress within the same state without requiring the transition to be
explicitly defined in the workflow (thereby clobbering the workflow).
The currently valid actions for WFX
-eligible transitions are
IMMEDIATE
: wfx instantly transitions to the transition's ending stateto
, andWAIT
: external north-bound input, e.g., by an operator or a higher-up hierarchical wfx is required to advance the workflow
with WAIT
being the default WFX
action.
For CLIENT
-eligible transitions, there are currently no pre-defined actions to assign to in a workflow specification.
Instead, the transition execution actions are encoded in the client implementations which are specific to a workflow (family).
Extending the available actions for WFX
-eligible transitions and providing actions for CLIENT
-eligible transitions is recorded in the Roadmap.
Beyond syntactic requirements such as, e.g., a particular set being non-empty or the uniqueness property of names, the following semantic rules and constraints are checked and enforced by wfx upon loading a workflow:
- There's exactly one initial state, i.e., there are no incoming transitions to this state with this state's name in
to
. - There are no unreachable states, i.e., states without an incoming transition.
- For each state, there can't be more than one outgoing transition whose
action
isIMMEDIATE
. - Transition tuples (
from
,to
,eligible
,action
) must be unique. - There are no cycles in the workflow graph except for trivial cycles, i.e. transitions where
from
equalsto
(used for e.g. progress reporting). - Each state belongs to at most one group.
Tip: wfxctl
can be used to validate workflows offline, e.g.
wfxctl workflow validate workflow/dau/wfx.workflow.dau.direct.yml
These definitions are illustrated in more detail in the following exemplary Kanban workflow.
A job is an instance of a workflow in which wfx and a client progress in lock-step. As a result, a job can only be created if the corresponding workflow already exists. Jobs can be driven by different workflows. This allows wfx to accommodate a variety of use cases and workflows that are tailored to specific needs. For example, firmware and container image updates may have different requirements and constraints that require separate workflows to address them.
A new job can be created by sending a JobRequest
object (see wfx OpenAPI Specification) to wfx's northbound REST API.
A JobRequest consists of the following:
- a non-empty
clientId
to assign the job to a specific client - a non-empty
workflow
name to select a workflow for the job - an optional array of
tags
that can be used to query the job - an optional job
definition
, which is a freeform JSON object that provides job-specific data to the client.
For example, the job definition
could contain a download URL for an encrypted firmware artifact that only the specific
client can decrypt.
When wfx accepts the JobRequest
, a new Job
entity is created, and the following fields are populated by wfx:
id
: the globally unique job idstate
: the unique initial state of the workflow that drives the jobstime
,mtime
: the date and time (ISO8601) when the job was createdstatus.definitionHash
: a hash value computed over thedefinition
field, used to detect jobdefinition
modifications.
After a job has been created, its definition
, status
, and tags
can be updated using wfx's REST APIs. If a job
definition
is updated after a client has already started working on the job, this assumes that the client can handle
the changes. To simplify things for client authors, wfx automatically updates the status.definitionHash
whenever the
definition
changes. This provides a mechanism for detecting changes in the definition
.
Whenever a job's status
or definition
changes, wfx prepends the current value to the job's history
array.
This allows for reviewing job updates later on and diagnosing problems.
Note: By default, the job history is omitted from all Job
responses, primarily due to its diagnostic nature but
also for bandwidth reasons. Clients requiring a job's historical data must explicitly request the specific job and
include the history=true
parameter in their request.
Jobs can be deleted using the northbound REST API. For example, this can be used to perform maintenance on old jobs. Note that wfx does not perform any housekeeping on its own.
An exemplary Kanban-inspired workflow YAML specification may be defined as follows:
name: wfx.workflow.kanban
groups:
- name: OPEN
description: The task is ready for the client(s).
states:
- NEW
- PROGRESS
- VALIDATE
- name: CLOSED
description: The task is in a final state, i.e. it cannot progress any further.
states:
- DONE
- DISCARDED
states:
- name: BACKLOG
description: Task is created
- name: NEW
description: task is ready to be pulled
- name: PROGRESS
description: task is being worked on
- name: VALIDATE
description: task is validated
- name: DONE
description: task is done according to the definition of done
- name: DISCARDED
description: task is discarded
transitions:
- from: BACKLOG
to: NEW
eligible: WFX
action: IMMEDIATE
description: |
Immediately transition to "NEW" upon a task hitting the backlog,
conveniently done by wfx "on behalf of" the Product Owner.
- from: NEW
to: PROGRESS
eligible: CLIENT
description: |
A Developer pulls the task or
the Product Owner discards it (see below transition),
whoever comes first.
- from: NEW
to: DISCARDED
eligible: WFX
description: |
The Product Owner discards the task or
a Developer pulls it (see preceding transition),
whoever comes first.
- from: PROGRESS
to: VALIDATE
eligible: CLIENT
description: |
The Developer has completed the task, it's ready for validation.
- from: PROGRESS
to: PROGRESS
eligible: CLIENT
description: |
The Developer reports task completion progress percentage.
- from: VALIDATE
to: DISCARDED
eligible: WFX
description: |
The task result has no customer value.
- from: VALIDATE
to: DISCARDED
eligible: CLIENT
description: |
The task result cannot be integrated into Production software.
- from: VALIDATE
to: DONE
eligible: CLIENT
description: |
A Developer has validated the task result as useful.
- from: VALIDATE
to: DONE
eligible: WFX
action: WAIT
description: |
The Product Owner has validated the task result as useful
resulting in the following graph representation:
BACKLOG
│
▼
NEW ───────┐
│ │
├◀─┐ │
▼ │ │
PROGRESS ─────┤
│ │
▼ │
VALIDATE ─────┤
│ │
▼ ▼
DONE DISCARDED
Assuming the preceding Kanban workflow is saved in the file wfx.workflow.kanban.yml
,
wfxctl workflow create --filter=.transitions wfx.workflow.kanban.yml
loads the Kanban workflow into wfx making it available to create jobs driven by it.
Note that the the command's output was made less verbose by using a --filter
to only show the transitions.
Henceforth, a job is identified with a "Task" and a state is identified with a "Lane" in Kanban board parlance. The Product Owner Parker as also owning and managing the Kanban "Board" instruments the northbound wfx management interface while Developers instrument the southbound client interface.
Now that the Kanban workflow is available, Product Owner Parker creates a new Backlog item ― overriding the Pull principle ― to be taken by the highly specialized Developer Dana:
echo '{ "title": "expose job api" }' | \
wfxctl job create --workflow wfx.workflow.kanban \
--client-id dana \
--filter='del(.workflow)' -
The Task is immediately transitioned to the "NEW" state (Kanban Lane) by wfx on behalf of the Product Owner Parker as
the transition's action
is IMMEDIATE
.
Note that the piped-in JSON document is the Job Definition (see wfx OpenAPI Specification) which is the contract
between the operator (Product Owner) creating jobs (Tasks) and the clients (Developers) executing those jobs so that
they're actually able to process the job.
Then, Developer Dana, knowing the job (Task) Identifier, pulls the task into "PROGRESS"
wfxctl job update-status \
--actor=client \
--id=1 \
--state=PROGRESS
and starts working on it.
Traditional tools like curl
can also be used instead of wfxctl
to achieve the same result:
curl -X PUT \
http://localhost:8080/api/wfx/v1/jobs/1/status \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"state":"PROGRESS"}'
Meanwhile, Developer Dana sporadically reports progress
wfxctl job update-status \
--actor=client \
--id=1 \
--state=PROGRESS \
--progress $((RANDOM % 100))
until having finished the task and progressing it into the "VALIDATE" state:
wfxctl job update-status \
--actor=client \
--id=1 \
--state=VALIDATE
Then, Developer Dana may realize that the task result cannot be integrated into the Production software and may put it into "DISCARDED" or Product Owner Parker may come to the conclusion that no customer value is provided by the task result, also putting it into "DISCARDED" ― whoever realizes this first.
Or, if the task result greatly increases customer value, Developer Dana as being highly experienced may do the validation herself, thereafter putting the task to "DONE". The Product Owner Parker may come to the same conclusion ― again whoever is faster in realizing the customer benefit.
Alternatively, if the task in question holds significant potential for increased customer value, Developer Dana, being highly experienced, may undertake the validation process herself and subsequently mark the task as "DONE." Likewise, Product Owner Parker may arrive at the same conclusion and act accordingly - again, whoever realizes this first may advance the task accordingly.