Sometimes we need to use long running workflows in our applications. Workflow execution can take a lot of time and has asynchronous nature. For an example: after document creation we must send email to a manager, with a link to a page for document publication. This is a very simple example, in real world apps workflows could be very complicated. Typical case is an internet store and workflow of purchase of goods. We have some ready to use solutions like Microsoft Workflow Foundation. But for some applications it is an overkill. Often we don’t want to allow users to create and edit workflows and want to use very simple(for developers) solutions, with all the power of static type checking. Lets write a requirements list.
Workflow description should looks like a plain c# function.
Workflow is a composition of activities and other workflows.
Activity describes what should be done in therms of domain model and should be represented as a POCO class.
Workflow execution and translation of activities to a real code should not be done by a workflow, but by some executor entity,which is orthogonal to workflows.
Workflow serialisation should be easy and without black magic like serialisation of expression trees and enumerators.
Ability to see current workflow state and all results of executed activities.
Ability to extend workflow engine by other features like timeouts, last activity undo and so on.
Lets start and define classes of activities.
Action is a base class for all activities. Ask and Show just two examples of concrete activities. So far so good.
Next stop is a storage class.
Very simple helper class, which serialises objects to json on a file system and vice versa. Now we should define workflow class. We probably will use workflows in this way:
Define a workflow
Create the workflow instance, probably with some params.
Invoke Execute Method
Check result, it could be some activity for execution or final result. In case of final result we can stop, but in other case we should execute returned activity.
Save workflow into storage.
On activity response, load workflow from storage and add a result.
Go to step 2
Lets describe it in code and specify behaviour of Ask and Show commands.
GetResult method is the essence of our solution, but it will not work in required way. Lets exam that function line by line and add descriptions of required behaviour.
Now we can express it in a real code
We could wrap return boilerplate into a return function, but we can’t wrap “if .. return ..” constructions into some helper function. Continuations to the rescue.
Now everything is ok, but it doesn’t look like a plain c# function. Could we do better? Definitely yes, our return and bind functions is a monad pattern and as usual we can use linq syntactic sugar.
Now we could rewrite our workflow function.
We still don’t know hot to implement IsExecuted and GetResult functions of a WokflowStep class. We should store, somehow, results of already executed activities in workflow. We can do it by incrementally assign numbers to activities on each Action creation and use List as a storage for serialised results. We will use Execution context class which will be used to store results of executed actions and keep current action index counter.
Now we must add context into Workflow and WokflowStep classes
We have done. Lets see some composable workflow in action.
Hm, seems that we meet all our requirements from the list.
Now we could use this engine in console apps, asp.net applications or do some cool SharePoint stuff. And monad pattern helps us to solve that problem in a beautiful way. We also can add support for loops, conditions(how), parallel execution, transactions and what ever you want. Full code for this post.
P.S.
Just imagine possibility to do computations over a database or web services without any IRepository, IService code bloat.