Re-using workflow definitions

In the last post I showed how to define a workflow;

var workflow = Workflow<CustomerState>.Definition()
  .Configure()
  .On<Exception>(() => Console.WriteLine("Caught an exception"))
  .When<CustomerState>()
  .Do<PlaceOrder>()
  .Do<PayForCoffee>()
  .Do<PickUp>();

Although this is fine for defining small workflows, this would be cumbersome for more complex workflows.

The frameworks AsAWorkflow<T> abstract class solves this problem.  All we have to do is create a derived class and copy the above code into the Configure() method;

public class CoffeeCustomerService : AsAWorkflow<CustomerState>
{
    // Obsolete contructor
    public CoffeeCustomerService(IDefine<CustomerState> workflow) : base(workflow)
    {
    }

    // Obsolete method
    public override void Configure(IDefine<CustomerState> workflow)
    {
    }

    public override IWorkflow<CustomerState> Configure()
    {
        return Workflow<CustomerState>.Definition()
          .Configure()
            .On<Exception>(() => Console.WriteLine("Caught an exception"))
            .When<CustomerState>()
            .Do<PlaceOrder>()
            .Do<PayForCoffee>()
            .Do<PickUp>();
     }
}

The constructor and parameterised Configure method are obsolete and will be removed in a future release.  The workflow is defined in the Configure() method as usual.

All the client has to do is instantiate this class and pass the initial state to the start method;

var coffeeCustomerWorkflow = new CoffeeCustomerService(new Workflow<CustomerState>());
var customerState = new CustomerState();
coffeeCustomerWorkflow.Start(customerState);

This is a bit tidier and more re-useable.

The next minor release will have a default constructor for this class as we plan to obsolete the parameterized constructor.

Simple workflows

It’s simple to define workflows declaratively;

var customerWorkflow = Workflow<CustomerState>.Definition()
    .Configure()
    .On<Exception>(() => Console.WriteLine("Caught an exception"))
    .When<CustomerState>()
    .Do<PlaceOrder>()
    .Do<PayForCoffee>()
    .Do<PickUp>();

The above workflow defines the ubiquitous Starbucks example.  The customer states are defined by the below class;

public class CustomerState 
{     
public bool OrderPlaced { get; set; }     
public bool Paid { get; set; }     
public bool DrinkReceived { get; set; }
}

Now the workflow steps can be defined as Operations on CustomerState;

public class PlaceOrder : BasicOperation<CustomerState>
{
    public override CustomerState Execute(CustomerState data)
    {
        Console.WriteLine("Place order;");
        data.OrderPlaced = true;
        return data;
    }
}

When starting the workflow the parameterised start method should be used;

var endState = customerWorkflow.Start(new CustomerState());

Now in nuget

The latest version can be installed via the NuGet gallery. In the Visual Studio package console window;

Install-Package objectflow.core -pre

Exception handling

I’ve added exception handling into the latest release (1.4.x).  This allows you to specify exception handling policies as below;

var workflow = new Workflow<string>();

workflow.Configure()
	.On<Exception>(MyExceptionHandler)
	.On<NotImplementedException>(MyNotIMplementedHandler)
	.When().Do(...)

The When<T>() method closes the definition of exception handlers and you can then define the workflow as usual.  The T in the when clause should match the type of workflow defined.  In this case, string.

My original attempt at handling errors by catching them and setting the operation’s success status to false was a bit of a mistake.  I allowed this behaviour to be over-ridden as I could see problems.

I’ve decided to break the backward compatibility as this previous exception handling mechanism was implemented under time pressure (work and family related) and this is how I envisioned it working initially.