Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure that a system level operation is atomic? any pattern?

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

like image 255
Sohail Raza Avatar asked Jan 22 '14 13:01

Sohail Raza


2 Answers

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.

like image 94
henginy Avatar answered Sep 23 '22 18:09

henginy


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());
    }
}
like image 37
Duncan Finney Avatar answered Sep 23 '22 18:09

Duncan Finney