22

Imagine of a WebForms application where there is a main method named CreateAll(). I can describe the process of the method tasks step by step as follows:

1) Stores to database (Update/Create Db items 3-4 times)

2) Starts a new thread

3) Result1 = Calls a soap service, and by using a timeout threshold it checks the status and after x minutes.The it continues (status now is OK and it isn't means failure)

4) Stores to database (Update/Create Db items 3-4 times)

5) result2 = Calls a soap service (In a fire and forget way)

6) Updates a configuration file (that is taken actually from result1)

7) By using callback requests it checks every x secs at front part the state of the result2 and the UI shows a progress bar.If the process is finished (100%) it means success

I am considering that all of them are tasks that can be grouped by their type.Basically the several types of actions are :

  • Type1: DB transaction
  • Type2: Service communication/transaction
  • Type3: Config file I/O transactions

I want to add a rollback/retry mechanism to the existing implementation and to use a task oriented architecture and refactor existing legacy code.

I found that something like Memento Design Pattern OR Command Pattern in C# could help for this purpose.I also found the msdn Retry Pattern description interesting. I don't realy know and I want someone to lead me to the safest and best decision...

Can you suggest me the best way for this case to keep the existing implementation and the flow but wrapping it in a general and abstract retry/rollback/tasklist implementation ?

The final implementation must be able to retry in every case (whatever task or general failure such as timeout etc throughout the general createAll process) and also there would be a rollback decision list where the app must be able to rollback all the tasks that was accomplished.

I want some examples how to break this coupled code.


PseudoCode that might be helpful:

class something
{  
    static result CreateAll(object1 obj1, object2 obj2 ...)
    {
        //Save to database obj1
        //...
        //Update to database obj1 
        //
        //NEW THREAD
       //Start a new thread with obj1, obj2 ...CreateAll
       //...          
     } 

    void CreateAllAsync()
    {
        //Type1 Save to database obj1
        //...
        //Type1 Update to database obj2

        //Type2 Call Web Service to create obj1 on the service (not async)

        while (state != null && now < times)
        {
            if (status == "OK")
            break;      
            else
            //Wait for X seconds
        }

        //Check status continue or general failure
        //Type1 Update to database obj2 and obj1

        //Type2 Call Web Service to create obj2 on the service (fire and forget)

        //Type3 Update Configuration File
        //Type1 Update to database obj2 and obj1
        //..   

    return;
}

//Then the UI takes the responsibility to check the status of result2
Giannis Grivas
  • 3,374
  • 1
  • 18
  • 38
  • 1
    You could check out the Transient Fault Handling from MS Pattern and Practices https://msdn.microsoft.com/en-us/library/hh675232.aspx It's extendable to account for other exceptions but handles both async and non async. – CharlesNRice May 23 '16 at 14:58
  • Would any generic retry mechanism that retries a delegate work for you? – usr May 25 '16 at 20:47
  • Which one do you suggest? I am trying to combine the command pattern (to break the existing code into specific tasks) with the Polly framework for retry policies – Giannis Grivas May 25 '16 at 20:54
  • @CharlesNRice can you give an example with Transient Fault Handling Application Framework? – Giannis Grivas May 26 '16 at 06:58

5 Answers5

13

Look at using Polly for retry scenarios which seems to align well with your Pseudo code. At the end of this answer is a sample from the documentation. You can do all sorts of retry scenarios, retry and waits etc. For example, you could retry a complete transaction a number of times, or alternatively retry a set of idempotent actions a number of times and then write compensation logic if/when the retry policy finally fails.

A memento patterns is more for undo-redo logic that you would find in a word processor (Ctrl-Z and Ctrl-Y).

Other helpful patterns to look at is a simple queue, a persistent queue or even a service bus to give you eventual consistency without having to have the user wait for everything to complete successfully.

// Retry three times, calling an action on each retry 
// with the current exception and retry count
Policy
    .Handle<DivideByZeroException>()
    .Retry(3, (exception, retryCount) =>
    {
        // do something 
    });

A sample based on your Pseudo-Code may look as follows:

static bool CreateAll(object1 obj1, object2 obj2)
{
     // Policy to retry 3 times, waiting 5 seconds between retries.
     var policy =
         Policy
              .Handle<SqlException>()
              .WaitAndRetry(3, count =>
              {
                 return TimeSpan.FromSeconds(5); 
              });

       policy.Execute(() => UpdateDatabase1(obj1));
       policy.Execute(() => UpdateDatabase2(obj2));
  }
Murray Foxcroft
  • 12,785
  • 7
  • 58
  • 86
  • It seems OP is not retrying, he it _polling_ that service. – Evk May 26 '16 at 07:49
  • @Evk - see points 3 and 7 in the question. There are "guards" required to keep retrying to see the process through, however this whole process could sit inside a larger polling type scenario to run on a schedule. For this I would look at using Quartz.Net http://www.quartz-scheduler.net/ – Murray Foxcroft May 26 '16 at 07:56
  • Both 3 and 7 looks like polling to me, which I think is not the same as "retry on failure" pattern. Just a little note to OP in case that's really polling (question is not super-clear). – Evk May 26 '16 at 11:21
  • Can you help about how can I break the code into tasks or something else (I am up to command pattern)? Example code would be great.Poly is great indeed.The others have also suggested the refactoring technique.Can you help me too?Thanks – Giannis Grivas May 31 '16 at 21:39
  • I have added a sample to my answer based on your CreateAll pseudo code. When you sayTasks, do you mean individual calls (for example, where I have re-used the policy for multiple operations), or are you considering parallel work (which you could run over a collection of your object types with a Parallel.ForEach(...)) or asynchronous operation where you could create a delegate/callback to notify you when the work has completed on a background thread? – Murray Foxcroft May 31 '16 at 22:53
  • The answer is both.I have all kind of transactions.I have to refactor this routine.. – Giannis Grivas Jun 01 '16 at 14:50
  • Then you will have a combination of all of the following techniques/patterns - retries, transactions, exception management, compensation, parallel execution and asynchronous Tasks. I'd try and draw it all out on paper or a whiteboard, storyboard scenarios and apply the patterns and then write the code. – Murray Foxcroft Jun 01 '16 at 16:06
  • @Giannis Grivas, Polly framework really makes sense for your scenarios. I've never used it before, but the documentation tells me that it would be nice to combine this technic with the approach from your recent topic... It supports async methods as well.. so, I would give it a try, anyway... – Johnny Svarog Jun 09 '16 at 17:46
  • `UpdateDatabase1` and `UpdateDatabase2` are ***transactional*** ? – Kiquenet Mar 16 '18 at 09:46
  • You would need to run them in the same context within a transaction - i.e. using one Polly statement. e.g. policy.Execute(() => UpdateDatabases(obj1, obj2)); – Murray Foxcroft Mar 16 '18 at 12:46
5

You can opt for Command pattern where each command contains all the necessary information like connection string, service url, retry count etc.On top of this, you can consider , data flow blocks to do the plumbing.

High level viewenter image description here:

Update: Intention is to have Separation Of Concern. Retry logic is confined to one class which is a wrapper to existing command. You can do more analysis and come up with proper command, invoker and receiver objects and add rollback functionality.

public abstract class BaseCommand
{
    public abstract RxObservables Execute();
}

public class DBCommand : BaseCommand
{
    public override RxObservables Execute()
    {
        return new RxObservables();
    }
}

public class WebServiceCommand : BaseCommand
{
    public override RxObservables Execute()
    {
        return new RxObservables();
    }
}

public class ReTryCommand : BaseCommand // Decorator to existing db/web command
{
    private readonly BaseCommand _baseCommand;
    public RetryCommand(BaseCommand baseCommand)
    {
         _baseCommand = baseCommand
    }
    public override RxObservables Execute()
    {
        try
        {
            //retry using Polly or Custom
            return _baseCommand.Execute();
        }
        catch (Exception)
        {
            throw;
        }
    }
}

public class TaskDispatcher
{
    private readonly BaseCommand _baseCommand;
    public TaskDispatcher(BaseCommand baseCommand)
    {
        _baseCommand = baseCommand;
    }

    public RxObservables ExecuteTask()
    {
        return _baseCommand.Execute();
    }
}

public class Orchestrator
{
    public void Orchestrate()
    {
        var taskDispatcherForDb = new TaskDispatcher(new ReTryCommand(new DBCommand));
        var taskDispatcherForWeb = new TaskDispatcher(new ReTryCommand(new WebCommand));
        var dbResultStream = taskDispatcherForDb.ExecuteTask();
        var WebResultStream = taskDispatcherForDb.ExecuteTask();
    }
}
Saravanan
  • 920
  • 9
  • 22
  • Can you be more specific with code sample ?Especially how can I wrap the general retry mechanism. – Giannis Grivas May 30 '16 at 21:43
  • @Thank you for the update.Last one is to give me an example with the retry implementation as others did.Do you have an alternative?It would be great.Thanks in advance! – Giannis Grivas May 31 '16 at 21:44
  • 1
    Retry will be more or less in the same line as others explained, may be with async all the way. – Saravanan May 31 '16 at 22:38
  • ***Retry*** for `DB` and `WebService` ? `2 RxObservables` (_dbResultStream and WebResultStream_). You do nothing with 2 **streams** in your sample code. – Kiquenet Mar 16 '18 at 09:55
3

For me this sounds like 'Distributed Transactions', since you have different resources (database, service communication, file i/o) and want to make a transaction that possible involves all of them.

In C# you could solve this with Microsoft Distributed Transaction Coordinator. For every resource you need a resource manager. For databases, like sql server and file i/o, it is already available, as far as i know. For others you can develop your own.

As an example, to execute these transactions you can use the TransactionScope class like this:

using (TransactionScope ts = new TransactionScope())
{
    //all db code here

    // if an error occurs jump out of the using block and it will dispose and rollback

    ts.Complete();
}

(Example taken from here)

To develop your own resource manager, you have to implement IEnlistmentNotification and that can be a fairly complex task. Here is a short example.

Community
  • 1
  • 1
Dieter Meemken
  • 1,937
  • 2
  • 17
  • 22
  • Maybe this one is good for the I/O operations but all the other rollback cases will have also some logic.So it covers only some part of my paradigm.Can you extend this more? thanks – Giannis Grivas May 31 '16 at 21:41
  • Especially for the service part, you first maybe have to check the protokoll, so that it is 'transaction enabled'. When you call a soap service, in case of a timeout you could implement the rollback. Since you are only reading information, this should work. So a relatively simple resource manager could do the job. – Dieter Meemken Jun 01 '16 at 08:18
2

Some code that may help you to achieve your goal.

public static class Retry
{
   public static void Do(
       Action action,
       TimeSpan retryInterval,
       int retryCount = 3)
   {
       Do<object>(() => 
       {
           action();
           return null;
       }, retryInterval, retryCount);
   }

   public static T Do<T>(
       Func<T> action, 
       TimeSpan retryInterval,
       int retryCount = 3)
   {
       var exceptions = new List<Exception>();

       for (int retry = 0; retry < retryCount; retry++)
       {
          try
          { 
              if (retry > 0)
                  Thread.Sleep(retryInterval);
              return action();
          }
          catch (Exception ex)
          { 
              exceptions.Add(ex);
          }
       }

       throw new AggregateException(exceptions);
   }
}

Call and retry as below:

int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);

Ref: http://gist.github.com/KennyBu/ac56371b1666a949daf8

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
Ye Peng
  • 183
  • 6
  • So, would you prefer something like your code instead of Polly for example? I will try your example. – Giannis Grivas May 25 '16 at 22:22
  • Just looked at Polly which has more complicated implementations but I feel a little overhead. I will say if a piece of simple code can achieve our goal, why we need introduce extra code. However it is totally up to your decision. – Ye Peng May 26 '16 at 13:48
  • This is not plagiarism. I read this piece code in both GitHub and stackoverflow. https://gist.github.com/KennyBu/ac56371b1666a949daf8 I don't know who is original code owner but it is under Gnu license in GitHub. Although I should have the reference in my post but I don't think it is mandatory. – Ye Peng May 27 '16 at 14:07
  • 1
    if copying code from the internet is plagiarism, we are all going to prison for a long, long time. – Seano666 May 27 '16 at 22:30
  • 1
    If copying an entire answer from another SO post is not plagiarism, then what is plagiarism? – ConnorsFan May 31 '16 at 20:23
  • plagiarism is when you quote something and don't cite the source. It was originally missing the reference. – Meirion Hughes Jun 01 '16 at 19:08
2

Well...sounds like a really, really nasty situation. You can't open a transaction, write something to the database and go walk your dog in the park. Because transactions have this nasty habit of locking resources for everyone. This eliminates your best option: distributed transactions.

I would execute all operations and prepare a reverse script as I go. If operation is a success I would purge the script. Otherwise I would run it. But this is open for potential pitfalls and script must be ready to handle them. For example - what if in the mid-time someone already updated the records you added; or calculated an aggregate based on your values?

Still: building a reverse script is the simple solution, no rocket science there. Just

List<Command> reverseScript;

and then, if you need to rollback:

using (TransactionScope tx= new TransactionScope()) {
    foreach(Command cmd in reverseScript) cmd.Execute();
    tx.Complete();
}
Tomaz Stih
  • 529
  • 3
  • 10