7

I have a method which internally performs different sub-operations in an order and at failure of any of the sub operation i want to Rollback the entire operation.

My issue is the sub-operations are not all database operations. These are mainly system level changes like adding something in windows registry, creating a folder at a specified path and setting permissions etc. the sub-operations can be more than this.

want to do somthing like this;

CreateUser(){

     CreateUserFtpAccount();

     CreateUserFolder();

     SetUserPermission();

     CreateVirtualDirectoryForUser();


     ....
     ....
     ....
     and many more

}

if last operation fails, i want to roll back all previous operations.

So, what is the standard way to do this? is there a design pattern do handle this?

Note: i'm using C#.net

Suraj Singh
  • 4,041
  • 1
  • 21
  • 36
  • 1
    did you look up **transactionscope**? -> http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28v=vs.110%29.aspx – Mario Stoilov Jan 22 '14 at 14:01

4 Answers4

4

Here's one way to do it:

Using the command pattern, you can create undoable actions. With each operation, you register the related commands, so that you can undo the executed commands when a fail condition occurs.

For example, this might all belong in a transaction-like context object that implements IDisposable and put in a using block. The undoable actions would be registered to this context object. On dispose, if not committed, "undo" is carried out for all registered commands. Hope it helps. The downside is you may have to convert some methods to classes. This might be a necessary evil though.

Code sample:

using(var txn = new MyTransaction()) {
  txn.RegisterCommand(new CreateUserFtpAccountCommand());
  txn.RegisterCommand(new CreateUserFolderCommand());
  txn.RegisterCommand(new SetUserPermissionCommand());
  txn.RegisterCommand(new CreateVirtualDirectoryForUserCommand());
  txn.Commit();
}

class MyTransaction : IDisposable {
  public void RegisterCommand(Command command){ /**/ }
  public void Commit(){ /* Runs all registered commands */ }
  public void Dispose(){ /* Executes undo for all registered commands */ }
}

class UndoableCommand {
  public Command(Action action) { /**/ }
  public void Execute() { /**/ }
  public void Undo{ /**/ }
}

Update:

You mentioned that you have hundreds of such reversible operations. In this case, you can take a more functional approach and get rid of UndoableCommand completely. You would register delegates instead, like this:

using(var txn = new MyTransaction()) {
  txn.Register(() => ftpManager.CreateUserAccount(user),
               () => ftpManager.DeleteUserAccount(user));
  txn.Register(() => ftpManager.CreateUserFolder(user, folder),
               () => ftpManager.DeleteUserFolder(user, folder));
  /* ... */
  txn.Commit();
}

class MyTransaction : IDisposable {
  public void Register(Action operation, Action undoOperation){ /**/ }
  public void Commit(){ /* Runs all registered operations */ }
  public void Dispose(){ /* Executes undo for all registered and attempted operations */ }
}

As a note, you'd need to be careful with closures with this approach.

Community
  • 1
  • 1
henginy
  • 2,041
  • 1
  • 16
  • 27
  • i have hundreds of such reversible commands(methods in diff classes) so cant make each method a separate class. i think, i need to go with simply if-else/try-catch logic. any other thoughts? – Sohail Raza Jan 28 '14 at 10:09
  • nice, i would use Func delegate instead of Action in Register method because my methods will always be accepting and returning a Object (DTO) which is what Action does not allowi think. or does it? – Sohail Raza Jan 29 '14 at 23:34
  • You're right, Action delegate is void and doesn't allow having a return value. You can use Func freely since the implementation of MyTransaction.Commit & .Dispose will be in your hands. – henginy Jan 30 '14 at 12:45
3

I think your best bet would be to encapsulate the execution and reversal of each step of the process. It will make a lot more easily read code than nested try-catch blocks. Something like:

public interface IReversableStep
{
    void DoWork();
    void ReverseWork();
}

public void DoEverything()
{
    var steps = new List<IReversableStep>()
    {
         new CreateUserFTPAccount(),
         new CreateUserFolder(),
         ...
    }
    var completed = new List<IReversableStep>();
    try 
    {
         foreach (var step in steps)
         {
              step.DoWork();
              completed.Add(step);
         }
    }
    catch (Exception)
    {
         //if it is necessary to undo the most recent actions first, 
         //just reverse the list:
         completed.Reverse(); 
         completed.ForEach(x => x.ReverseWork());
    }
}
Duncan Finney
  • 2,054
  • 1
  • 12
  • 15
  • Please keep in mind the possible exception that occurs in `ReverseWork`. If this happens, only a partial rollback will be performed. – Caramiriel Jan 22 '14 at 14:25
  • 1
    Also you probably want to do the reversal steps in reverse order, not the same order the commands were done in. But I do like the concept! – James S Jan 22 '14 at 14:28
1

Both NTFS and the Registry support enrollment in KTM and MS DTC transactions (and by extension, TransactionScope). However, the transactional file system has been deprecated due to complexity, and may not be in some future version of windows.

If not everything fits in a transaction, I would certainly look to the command history patterns presented in other answers to this question.

Mitch
  • 21,223
  • 6
  • 63
  • 86
0

I'm not aware of any standard pattern for this type of thing but I'd probably do it with nested try/catch blocks myself - with appropriate code for the rollback of non-database operations in the catch block. Use a TransactionScope to ensure all the database operations are transactionactional.

eg:

using (TransactionScope scope)
{
  try
  {
     DoOperationOne();

     try
     {
       DoOperationTwo();

       DoDataBaseOperationOne(); // no need for try/catch surrounding as using transactionscope

       try
       {
         DoOperationThree();
       }
       catch
       {
         RollBackOperationThree();
         throw;
       }
     }
     catch
     {
       RollBackOperationTwo();
       throw;
     } 
  }
  catch
  {
    RollbackOperationOne();
    throw;
  }

  scope.Complete(); // the last thing that happens, and only if there are no errors!
}
James S
  • 3,558
  • 16
  • 25