Concepts
IdentityFlow keeps two things apart: the code that describes a process, and the record of what one run of that code did. The code is a Workflow Definition. The record is the event history of an instance.
This page defines the terms the rest of the documentation uses. You don’t need to read it first: start with the tutorial, and look a term up here when a page links to it.
| Term | Section |
|---|---|
| Workflow Definition, version, draft | Workflow Definition |
| instance, instance status | Instance |
| step, dialog, step status | Steps |
| event, projection | Events and projections |
| replay | Replay |
| binding, connector, fixture | Bindings and connectors |
| account, provider, principal | Accounts, providers and principals |
| assignee, assignee source | Assignees and assignee sources |
| access function | Access functions |
| starter, Workflow Test distribution | Starter and Workflow Test distribution |
A Workflow Definition is the value a defineWorkflow call returns and your
module exports as its default export. It has a name, a version, an optional
input schema, and an async function that contains the process logic. The host
deploys only the default export, so one file holds one workflow.
- Immutable. The code behind a deployed name and version never changes. To
change behavior, edit the definition in its file and raise its
version, keeping the samename. If the code behind a deployed name and version changes, the host refuses to start withWF_DEFINITION_BUNDLE_CHANGED. - Stored. The host keeps every deployed version in its database. Running instances load their version from there, so they keep running after the old code is gone from your project.
- Versioned. Versions follow semantic versioning. Without a
version, the host assigns0.0.0+<hash>; set one explicitly. - Running instances move to compatible versions. Each time an instance
continues, IdentityFlow runs it with the newest deployed version that is
compatible with its current one, in the sense of the semver range
^<version>. From1.0.0on, that is every later minor and patch version of the same major version; below1.0.0, the range is narrower. So an instance started on1.0.0that waits three weeks for an approval continues on1.2.0if that version was deployed in the meantime. A change that running instances cannot absorb needs a new major version; Author workflows lists what a minor or patch version may change. draft. A definition is a draft unless it setsdraft: false. The host skips drafts at startup and logsSkipping deployment of draft workflow. Workflow Tests run a definition whether or not it is a draft.
Author workflows walks through a complete definition. Deploy a new version of a workflow shows a new version with a test.
An instance is one run of one Workflow Definition. Starting a workflow creates an instance; the instance owns its own event history.
An instance has one of five statuses:
| Status | Meaning |
|---|---|
ACTIVE | The instance is running or waiting. Waiting for a person, a timer, an external system or a child workflow shows on the step, not on the instance. |
PAUSED | An operator paused it. Timers and retries wait until an operator resumes it, or until the resume time set when pausing. A dialog can still be answered; the workflow continues after the resume. |
TERMINATED | An operator ended it. It does not run again. |
COMPLETED | The workflow function returned. The returned value is the instance result. |
ERRORED | The workflow function threw an error it did not catch, usually a step that failed on its last attempt. It does not run again. |
COMPLETED, ERRORED and TERMINATED are final. Errors and retries
explains how an instance reaches ERRORED.
Operate running workflows opens with a figure of which commands move an instance between these statuses.
A step is a named unit of work or waiting inside an instance. There are five kinds:
| Step | What it does | Status while it waits |
|---|---|---|
flow.do | Runs your code, typically a call to another system, and records the result. | ACTIVE |
flow.sleep | Waits for a duration or until a date. | SLEEPING |
flow.dialog | Waits for a person to answer. A dialog is this kind of step. | PENDING |
flow.request | Starts work in an external system and polls it for the result. | REQUESTING |
flow.start | Starts a child workflow, a separate instance, and waits for its result. | RELYING |
A result checked against a schema passes through CONTINUING:
Between two attempts of a failed step, the step is also ERRORED, with the
time of its next attempt; it becomes ACTIVE again when that attempt starts.
It is finally ERRORED only when no attempt is left. A step can also end as
CANCELED when Promise composition stopped waiting for it; see
Promise composition.
The step name identifies the step within its instance. It has to be stable: a resumed run finds its recorded work by that name.
An event is an immutable record that something happened to an instance or
one of its steps, for example INSTANCE_STARTED, STEP_PENDING or
STEP_COMPLETED. Events are appended and never changed or deleted. They are the
source of truth for an instance’s state and its audit trail.
A projection is a queryable view built from events, such as the list of dialogs waiting for one person. The IdentityFlow UI and the GraphQL API read projections. IdentityFlow updates the projections as it appends each event. The events are the record; 0.3.0 has no command that rebuilds a projection from them.
IdentityFlow does not keep your workflow function in memory while an instance waits. When the instance continues, after an answer, a timer, a retry or a restart of the host, IdentityFlow runs the workflow function again from the top. This is replay.
During replay, every step that already finished returns its recorded result from the event history. Its code does not run again. The first step without a recorded result runs for real, and execution continues from there.
A short example:
- The first run calls
flow.do('load employee', …), which calls the directory and records{ displayName: 'Alex' }. Thenflow.dialog(…)waits for an answer. The run stops. - Two days later someone answers. The function runs again from the top.
flow.do('load employee', …)returns the recorded{ displayName: 'Alex' }without calling the directory.flow.dialog(…)returns the recorded answer. The code after it runs for the first time.
The step name is the key into the recorded results:
flow.do('load employee', () => directory.lookup(flow.params.employeeId))- reuses completed result
- finds the recorded result
- not called on replay
The consequence: code outside a step runs on every replay. It must not call
another system, read the clock, use random numbers or write anything. Put that
work inside a flow.do step, where its result is recorded and returned on
replay.
Code outside steps may read flow.params, branch on step results and call
further steps. Replay rules lists
the rules.
A binding is a workflow’s named access to an external system, such as a
directory or a payroll service. You declare it with
defineBinding(name, factory); the factory creates the client. The workflow
gets the client with flow.use(binding) and calls it inside a step.
A connector is the protocol implementation behind a binding. IdentityFlow 0.3.0 ships one connector, for GraphQL. For any other protocol, the binding’s factory builds your own client.
A fixture is the replacement for a binding in a Workflow Test: an object your test registers under the binding’s name, so the workflow never reaches the real system. In other tools this is a fake or test double.
Bindings and connectors shows how to write a binding. Test workflows shows fixtures.
An account is a signed-in identity from your OpenID Connect provider. An account identifies who someone is, not what they may do.
A provider is a registered identity hook with an ID, such as acme. For an
account that signs in, the provider reports what that account is in your
organization.
A principal is one such fact: a { providerId, subject } pair, for example
{ providerId: 'acme', subject: 'role:approver' }. The subject is a string the
provider chooses. It can name a person (user:42), a role or a group. A
principal grants nothing by itself; a dialog or an access rule has to name it.
Identity and access explains how IdentityFlow chooses the provider for a login, and Connect your identity provider shows how to write one.
An assignee is a principal that may answer a dialog. A dialog lists its assignees when it starts. An account may answer when one of its current principals matches one of the assignees. IdentityFlow asks the provider for the account’s principals again at the moment the account answers.
An assignee source is a reference to a group maintained in another system, such as “the approvers of cost center 4711”. A dialog can carry one next to its assignees. When that system sends a change event, a signed webhook call to IdentityFlow, the assignees of the open dialogs that use the source are resolved again. Assignee sources explains the details.
An access function is one of four kinds of access an account can receive for
a workflow family, the versions of one workflow name in one release channel:
| Access function | Allows |
|---|---|
starter | Starting the workflows. |
viewer | Reading their instances in the Business view. |
operator | The Operations view: pausing, resuming and terminating instances, reassigning dialogs, and replaying failed assignee-source refreshes (Failed work). |
auditor | Reading their audit records in the Audit view. |
Access control shows how to grant access functions.
The starter is the example project delivered with IdentityFlow. It contains sample workflows, hooks, tests and scripts to run everything locally.
The Workflow Test distribution is the test runner delivered with each release, next to the IdentityFlow program. It runs Workflow Tests against a real PostgreSQL database. It is not published on npm.
Next: Author workflows.