Tuesday, December 30, 2014

Orchestrator pattern in .Net - Using TPL

This is continuation of my previous posts to find out the best mechanism to sequence or orchestrate operations in .Net. Running in parallel is added advantage in the library.
  1. State pattern v/s Orchestrator
  2. WWW - Passing arguements to custom activity inside a sequence container in coded workflow
  3. Orchestration pattern in .Net - Using TPL

Using TPL to run tasks sequentially

Though the word TPL stands for Task Parallel Library, we can use it to execute tasks in sequence. One example is given below.

    class TPLTest
    {
        internal void Test()
        {
            Task.Factory.StartNew(new Action(Step1))
                .ContinueWith(new Action<Task>(task => Step2()))
                .ContinueWith(new Action<Task>(task=>Step3()));           
        }
        private void Step1()
        {
            Console.WriteLine("step 1 start");
            Thread.Sleep(2000);
            Console.WriteLine("step 1 end");
        }
        private void Step2()
        {
            Console.WriteLine("step 2 start");
            Thread.Sleep(2000);
            Console.WriteLine("step 2 end");
        }
        private void Step3()
        {
            Console.WriteLine("step 3 start");
            Thread.Sleep(2000);
            Console.WriteLine("step 3 end");
        }
    }

Passing context object into steps

In normal business applications, when we deal with this type of sequential operation, there would be one or more contextual objects which needs to be modified as part of the execution. Either the result of one step needs to be passed as parameter to next step or one contextual state object will be shared by all the steps.

All steps sharing same state object

Its very simple to achieve using TPL. Just keep the state object in the class level so that all methods can access it.


    class TPLTest
    {
        //State kept as int for demo purpose only. In actual applications the state object will be more complicated
        int state = 0; 
        internal void Test()
        {
            Task mainTask = new Task(new Action(Step1));
            mainTask.ContinueWith(new Action<Task>(task => Step2()))
            .ContinueWith(new Action<Task>(task => Step3()));
            mainTask.Start();
        }
        private void Step1()
        {
            Console.WriteLine("step 1 start. state ="+state.ToString());
            Thread.Sleep(2000);
            state = 1;
            Console.WriteLine("step 1 end. state =" + state.ToString());
        }
        private void Step2()
        {
            Console.WriteLine("step 2 start. state =" + state.ToString());
            Thread.Sleep(2000);
            state = 2;
            Console.WriteLine("step 2 end. state =" + state.ToString());
        }
        private void Step3()
        {
            Console.WriteLine("step 3 start. state =" + state.ToString());
            Thread.Sleep(2000);
            state = 3;
            Console.WriteLine("step 3 end. state =" + state.ToString());
        }
    }

Executing in pipeline mode / Output of one step is fed as input to next step

Below is the code snippet which shows how the TPL can be used to execute tasks sequentially by feeding the output of one step as input to next step.

   class TPLTest
    {
        internal void TestPipeLine()
        {
            //int is used as input for demo purpose only. 
            //In actual application the input object will be more complicated class
            int input = 1;
            //There is no typed way to pass input to the pipeline.It accepts object and explicit conversion is required to get int value.
            Task<int> result = Task.Factory.StartNew<int>((arg) => Convert.ToInt32(arg),input)
                .ContinueWith(antTask => StepA(antTask.Result))
                .ContinueWith(antTask => StepB(antTask.Result))
                .ContinueWith(antTask => StepC(antTask.Result));
            Console.WriteLine("Result " + result.Result.ToString());
        }
        private int StepA(int arg)
        {
            return arg + 2;
        }
        private int StepB(int arg)
        {
            return arg * 2;
        }
        private int StepC(int arg)
        {
            return arg - 1;
        }
    }

We can see the result is printed as  5 (First step convert the object 1 to int 1 then + 2 then *2 then -1).

Honor SRP - Moving actions to separate classes

In the above examples we can see that the steps are staying in one class. This violates the SRP(Single Responsibility Principle). There are more than one reason for that class to change. So re-factoring little bit to honor SRP by moving the steps to separate classes.

    class TPLTest
    {
        int state = 0;
        internal void Test()
        {
            Task.Factory.StartNew(()=>new Step1().Execute())
                .ContinueWith((task)=> new Step2().Execute())
                .ContinueWith((task => new Step3().Execute()));
        }
    }
    class Step1
    {
        internal void Execute()
        {
            Console.WriteLine("step 1 start.");
            Thread.Sleep(2000);
            Console.WriteLine("step 1 end.");
        }
    }
    class Step2
    {
        internal void Execute()
        {
            Console.WriteLine("step 2 start.");
            Thread.Sleep(2000);
            Console.WriteLine("step 2 end.");
        }
    }
    class Step3
    {
        internal void Execute()
        {
            Console.WriteLine("step 3 start.");
            Thread.Sleep(2000);
            Console.WriteLine("step 3 end.");
        }
    }

Now the step classes will change, only if there is change in the step logic and the main class will change only if there is change in the step order.

Now we have seem how the TPL can be used to fulfill the requirements of orchestration. Below are the pros and cons of using TPL for sequential execution.

Pros

  • Its inbuilt in .Net. We don't need to invent or refer other assembly.
  • The Task objects can be reused in case we want to run in parallel.

Cons

  • Difficult to organize each step in different class in type safe way as there is no base class for steps to inherit from.
  • Out of the box it does't have the support to accept typed context object into the steps. ie we need to convert the object into the context / state object in first step.
  • From the development team perspective, its little difficult to use if all needs to follow same orchestration method. We can see there is very little control in the framework. Look at the first 2 snippets. The Test() 7 TestInPipeline() method is implemented differently but works properly. So there are chances that, we can see 10 different implementations in same code base after 6 months if coded by 10 different developers.

Conclusion

This can be used if the development team is disciplined to follow same mechanism else we will end up in different methods to achieve orchestration. Also there is no object oriented support to organize or control the step class coding. I am continuing my research to find better library for orchestration.

No comments: