Fundamental SpiffWorkflow Concepts ================================== Overview -------- The purpose of this library is to provide a general workflow execution enviroment with a wide variety of built-in task types that support many different workflow patterns. SpiffWorkflow keeps track of task dependencies and states and provides the ability to serialize or deserialize a workflow that has not been completed. Developers using this library can focus on displaying a workflow’s state and presenting its tasks to users of their application. .. _specs_vs_instances: Specifications vs. Instances ---------------------------- SpiffWorkflow consists of two different categories of objects: - **Specification objects**, which represent definitions of structure and behavior and derive from :code:`WorkflowSpec` and :code:`TaskSpec` - **Instance objects**, which represent the state of a running workflow (:code:`Workflow`/:code:`BpmnWorkflow` and :code:`Task`) For workflows, a specification is model of the workflow, an abstraction that describes *every path that could be taken whenever the workflow is executed*. An instance is an execution of a specification. It describes *the current state* or *the path(s) that were actually taken when the workflow ran*. For tasks, a specification is a model for how a task behaves. It describes the mechanisms for deciding *whether there are preconditions for running an associated task*, *how to decide whether they are met*, and *what it means to complete (successfully or unsuccessfully)*. An instance describes the *state of the task, as it pertains to the workflow it is part of* and *contains the data used to manage that state*. Specifications are unique, whereas instances are not. There is *one* model of a workflow, and *one* specification for a particular task. The model can be executed many times, and within one execution, a task spec may also be reached many times. Imagine a workflow with a loop. The loop is defined once in the specification, but there can be many tasks associated with each of the specs that comprise the loop. In our BPMN example (:ref:`quickstart`), we described a product selection process: Start -> Select and Customize Product -> Continue Shopping? Since the customer can potentially select more than one product, how our instance looks depends on the customer's actions. If they choose three products, then we get the following path:: Start --> Select and Customize Product -> Continue Shopping? -> | /-------------------------------------------------------- / |-> Select and Customize Product -> Continue Shopping? -> | /-------------------------------------------------------- / |-> Select and Customize Product -> Continue Shopping? There is *one* TaskSpec describing product selection and customization and *one* TaskSpec that determines whether to add more items, but it may execute any number of times, resulting in as many Tasks for these TaskSpecs as the number of products the customer selects. A Task Spec may have multiple inputs (if there are multiple paths to reach it) but a Task has only one parent. A specification may contains cycles, but an instantiated workflow is *always* a tree. .. _states: Understanding Task States ------------------------- * **PREDICTED** Tasks A predicted task is one that will possibly, but not necessarily run at a future time. For example, if a task follows a conditional gateway, which path is taken won't be known until the gateway is reached and the conditions evaluated. There are two types of predicted tasks: - **MAYBE**: The task is part of a conditional path - **LIKELY** : The task is the default output on a conditional path * **DEFINITE** Tasks Definite tasks are certain to run as the workflow progresses. - **FUTURE**: The task will definitely run. - **WAITING**: A condition must be met before the task can become **READY** - **READY**: The preconditions for running this task have been met - **STARTED**: The task has started running but has not finished * **FINISHED** Tasks A finished task is one where no further action will be taken. - **COMPLETED**: The task finished successfully. - **ERROR**: The task finished unsucessfully. - **CANCELLED**: The task was cancelled before it ran or while it was running. Tasks start in either a **PREDICTED** or **FUTURE** state, move through one or more **DEFINITE** states, and end in a **FINISHED** state. State changes are determined by task spec methods. Hooks ----- SpiffWorkflow executes a Task by calling a series of hooks that are tightly coupled to Task State. These hooks are: * `_update_hook`: This method will be run by a task's predecessor when the predecessor completes. The method checks the preconditions for running the task and returns a boolean indicating whether a task should become **READY**. Otherwise, the state will be set to **WAITING**. * `_on_ready_hook`: This method will be run when the task becomes **READY** (but before it runs). * `run_hook`: This method implements the task's behavior when it is run, returning: - :code:`True` if the task completed successfully. The state will transition to **COMPLETED**. - :code:`False` if the task completed unsucessfully. The state will transition to **ERROR**. - :code:`None` if the task has not completed. The state will transition to **STARTED**. * `_on_complete_hook`: This method will be run when the task's state is changed to **COMPLETED**. * `_on_error_hook`: This method will be run when the task's state is changed to **ERROR**. * `_on_trigger`: This method executes the task's behavior when it is triggered (`Trigger` tasks only). Task Prediction --------------- Each TaskSpec also has a `_predict_hook` method, which is used to set the state of not-yet-executed children. The behavior of `_predict_hook` varies by TaskSpec. This is the mechanism that determines whether Tasks are **FUTURE**, **LIKELY**, or **MAYBE**. When a workflow is created, a task tree is generated that contains all definite paths, and branches of **PREDICTED** tasks with a maximum length of two. If a **PREDICTED** task becomes **DEFINITE**, the Task's descendants are re-predicted. If it's determined that a **PREDICTED** will not run, the task and all its descendants will be dropped from the tree. By default `_on_predict_hook` will ignore **DEFINITE** tasks, but this can be overridden by providing a mask of `TaskState` values that specifies states other than **PREDICTED**. Where Data is Stored -------------------- Data can ba associated with worklows in the following ways: - **Workflow data** is stored on the Workflow, with changes affecting all Tasks. - **Task data** is local to the Task, initialized from the data of the Task's parent. - **Task internal data** is local to the Task and not passed to the Task's children - **Task spec data** is stored in the TaskSpec object, and if updated, the updates will apply to any Task that references the spec (unused by the :code:`bpmn` package and derivatives).