Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

An Idea of F# Workflow Engine based on Concurrency and Coordination Runtime

4.82/5 (8 votes)
20 Feb 2009CPOL3 min read 34K  
This article describes the basic ideas of how to build workflow engine a-la WF based on F# workflows and CCR

Introduction

This article describes basic ideas of how to build workflow engine a-la Workflow Foundation based on F# workflows and CCR.

Main Idea

Recently I've played a bit with F# workflows feature and one crazy idea appeared in my fevered imagination: “Would it be possible to build a workflow engine a-la Windows Workflow Foundation using F# workflows?”. In this article, you can see an attempt to build such an engine, at least a very initial version of it. First of all, I would like to provide you with some useful links for those who haven't had a chance to play with F# and F# workflows in particular:

So, if you continue reading this, then you probably already have some F# knowledge or have checked the links above… or just want to check what this crazy guy is talking about.

Let’s start with a small sample:

let workflowHost = new WorkflowEngine()

let fibonacciWorkflow number = 
    workflowHost {
                do printfn "Started!"
                let! f1 = fib number
                let! f2 = fib (number + 1)
                do printfn "Finished"
                do printfn "Result is %s" ((f1 + f2).ToString())
                return 0 //success
     }

runWorkflow(fibonacciWorkflow(22));;  

What do we have here: A workflow or THE workflow called fibonacciWorkflow. Something very trivial: we calculate two Fibonacci numbers and print out the sum of these numbers. Here fib function calculates Fibonacci number for a given position. Honestly, I've copy pasted this function from somewhere on the web. :)

let fib n =
    let rec fib_aux (n, a, b) =
        match (n, a, b) with
          | (0, a, b) -> a
          | _ -> fib_aux (n - 1, a + b, a)
    fun () -> fib_aux (n, 0, 1)		 

Looks like nothing special - a very trivial workflow. Let's have some fun now - it's time to have a closer look at the workflow engine.

Of course, each dependable workflow engine should manage threading and execute all workflows and activities in parallel (or not?) threads where possible and tries to do its best to increase performance. So our engine tries to follow the tradition and uses CCR to execute all activities (activities you can identify by “let!” keyword). Here we have two activities and each one is transformed behind the scene to CCR task. The output of each activity (CCR task) is taken as an input to the next activity (CCR task).

Before I show the actual implementation, I'd like to describe one more feature of our workflow engine.

The second thing each dependable workflow engine should do is to provide developers with the ability to inject aspects during activity execution. Let’s define trivial security aspect which is intended to notify someone (probably a system administrator) about a security alert (let's assume that in your company it is forbidden to calculate Fibonacci numbers :)) :)

let securityAspect() =
    printfn "Security Alert: Wake up, someone is running your workflow!!!"

This is a very, very simple aspect. I could make it more complicated (for instance, validate activity input within aspect)… maybe next time. :)

Now it’s time to have a look at the workflow engine code (by the way, please forgive me for using “engine” term here, in F# usually is used workflow builder term): 
type Activity<'a> = unit -> 'a

let runWorkflow (f:Activity<'a>) = f()

type WorkflowEngine() =
    let dispatcher = new Dispatcher(8, "MyDispatcher") //from Microsoft.Ccr.Core
    let queue = new DispatcherQueue("MyQueue", dispatcher) // from Microsoft.Ccr.Core
    
    member b.Let(p, rest) =
        rest p
    member b.Return(x) = 
        fun () -> x
    member b.Delay f = 
        fun () -> (runWorkflow f)()
    member b.Bind(p, rest) = 
        securityAspect()
        fun () -> 
            let res = queue.Enqueue(new Task( 
                                        fun () -> 
                                            printfn "Thread id is: %d" 
					Thread.CurrentThread.ManagedThreadId
                                            let res = p()
                                            let b = queue.Enqueue(new Task(
                                                                        fun () ->
                                                                           let fakeVal =
								    rest(res)()
                                                                            ()
                                                ))
                                            ()
                                        ))
            new 'b()//return fake value - just to fool the F# compiler :)... sorry, 
	           //it's ugly, but I haven't yet found a better way

Each keyword in the workflow is transformed to a method in the workflow builder: let -> Let, let! -> Bind, return -> Return and Delay method is used to provide invocation point for the whole workflow. Actually, all workflow-engine-features-I-have-stated-above-related functionality is implemented within the Bind method. It is possible to define additional keywords, like for, while, use, if/then… etc. Here I've implemented only the basic set of all possible keywords.

The important point here is that with the help of F# compiler, you have reasonable level of control over each line of code within the workflow and can influence it with aspects or any other ways you want.

The other important point is that within workflow, you can keep state of workflow execution.

Conclusion

Here I presented the basic ideas of how to build a workflow engine based on F# workflows. The engine provided above is pretty trivial and its mission is to convey a basic idea of possible implementation. There is absolutely no support for centralized exception handling (dependable workflow engine should also provide it), which could be achieved through output ports in CCR. And CCR usage is pretty superficial, I use it in a very simple and dummy way. You can find much better CCR-related articles if you want to learn it (I've used some CCR samples as a base).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)