Stateful Workflows

Stateful workflows are much like regular workflows except with some features that make them more ideal for workflow patterns that interact with people and roles inside of a database or web application. To use stateful workflows, reference objectflow.stateful as well as objectflow.core.

Using Yield

One of the basic things a workflow should be able to do is to pass control flow from the workflow back to the main process. This is accomplished by Yield:

var wf = new StatefulWorkflow<SiteVisit>("Site visit workflow")
  .Do(x => x.GatherInformation())
  .Yield("wait for approval")
  .Do(x => x.CloseVisit());

In this simple example, the first time Start() is called it runs from the beginning of the workflow to .Yield(). This is where you would typically persist the SiteVisit object in a database. When the object (either resurrected from the database or the same object) is "Started" again, it calls visit.CloseVisit() and exits the workflow. Now, let's discuss what those strings are for.

Storing the state

Objects that pass through a StatefulWorkflow must implement IStatefulObject. This interface has two methods: GetStateId(object workflowId) and SetStateId(object workflowId, object stateId). The two concepts here are a workflow ID and a state ID.

In the workflow above, the ID of the workflow is "Site visit workflow", which is passed to the StatefulWorkflow constructor. The workflow identifier ties a state to a particular workflow and makes it possible to have a single object passing through many workflows simultaneously. The state ID keeps track of the state. When the object isn't in a workflow, state ID is null, and when it reaches .Yield("wait for approval") in the example above, the state ID is set to "wait for approval".

This is a good example of what SiteVisit might look like:

public class SiteVisit : IStatefulObject {
  public virtual int Id { get; set; }
  // ... other properties
  public virtual string StateId { get; set; }
  public virtual string WorkflowId { get; set; }

  #region IStatefulObject members
  public object GetStateId(object workflowId) {
    if (object.Equals(workflowId, "Site visit workflow"))
      return StateId;
    else return null;
  }

  public void SetStateId(object workflowId, object stateId) {
    if (object.Equals(workflowId, "Site visit workflow")) {
      WorkflowId = workflowId;
      StateId = stateId;
    }
  }
  #endregion
}

Handling rejection

Many real-life workflows allow managers to either reject or "sign-off" on an object. If the object is rejected, it usually cycles back to a former state so that errors can be fixed. This sort of approvals concept is implemented by declared operations. For instance, we can add approvals to our workflow:

var startState = Declare.Step();
var wf = new StatefulWorkflow<SiteVisit>("Site visit workflow")
  .Define(defineAs: startState)
  .Do(x => x.GatherInformation())
  .Yield("wait for approval")
  .When(x => x.IsApproved, otherwise: startState)
  .Do(x => x.CloseVisit());

Now, if the site visit fails the IsApproved test it is cycled back to the open state until it hits the next Yield. While we defined startState before it was used in this case, that isn't necessary. For instance, we can also skip to the end.

Further reading

Stateful Security
Transitions

Last edited Mar 29, 2011 at 4:08 AM by tkellogg, version 5

Comments

oshachor Jan 29, 2012 at 10:51 AM 
I believe statefull flow is practically a State Machine.
If so, what are the benefits of using it over a state machine?