HiveBrain v1.2.0
Get Started
← Back to all entries
debugcsharpMinor

Making an atomic transaction out of several operations

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
operationsseveraltransactionmakingoutatomic

Problem

I'm looking for a design pattern or suggestions that can help refactor my code into something a bit less repetitive. I have a method that has several sequential steps (10-15) that if any of them fail must record detail about the failure to a log and rollback all previous portions of the transaction. The example is below.

I have thought about taking each try catch and turning it into an individual method but then I have to pass around my undo stack (among several other variables) and I still have to conditionally abort the method and process the undo stack. I've briefly looked into the Momento and Command patterns but both seemed to grow the line count by a fair margin.

EnumResult TransactionThatRollsBack()
{
        var undoLog = new Stack();
        try
        {
            MoveInventory(inv, src, dest);
            undoLog.Push(() => MoveInventory(inv,dest,src));
        }
        catch (Exception ex)
        {
            RecordError("InventoryError", ex, inv, src, dest);
            Undo(undoLog);
            return EnumResult.FailedInventoryMove;
        }

        try
        {
            NotifyReportingOfMove(inv, user);
            undoLog.Push(() => NotifyReportingOfUnMove(inv, user));
        }
        catch (Exception ex)
        {
            RecordError("NotifyReportingOfMove", ex, inv, user);
            Undo(undoLog);
            return EnumResult.FailedReportingMove;
        }

        try
        {
            AddUserToSweepstakes(user);
            undoLog.Push(() => RemoevUserFromSweepstakes(user));

        }
        catch (Exception ex)
        {
            RecordError("AddUserToSweepstakes", ex, user);
            Undo(undoLog);
            return EnumResult.FailedSweepstakesAdd;
        }

        ...
        return EnumResult.Success
}

Solution

You can remove repeated code if you record all your actions for executions and then execute them all at once with some generic exception handling. The idea is below.

Sorry for some possible lapses, didn't write in C# for quite some time. However I hope that you'll find the whole idea to be helpful.

EnumResult TransactionThatRollsBack()
    {
         var undoLog = new SmartStack();
         undoLog.Push(() => MoveInventory(inv,dest,src), EnumResult.1);
         undoLog.Push(() => NotifyReportingOfUnMove(inv, user), EnumResult.2);
         undoLog.Push(() => RemoevUserFromSweepstakes(user), EnumResult.3);

         undoLog.execute();
    }

    class SmartStack {
       SortedDictionary actions; 

       void push(Action a, EnumResult r) {
         actions.add(a, r);
       }

       void execute() {
         Stack completedActions = new Stack();
         foreach(KeyValuePair pair in actions) {
             try {
               pair.Key.Invoke();
               completedActions.push(pair.Key);
             }
             catch(Exception ex) {
               for (Action a in completedActions) {
                   Undo(a);
               }
               return pair.Value;
             }
         }
       }
    }

Code Snippets

EnumResult TransactionThatRollsBack()
    {
         var undoLog = new SmartStack();
         undoLog.Push(() => MoveInventory(inv,dest,src), EnumResult.1);
         undoLog.Push(() => NotifyReportingOfUnMove(inv, user), EnumResult.2);
         undoLog.Push(() => RemoevUserFromSweepstakes(user), EnumResult.3);

         undoLog.execute();
    }

    class SmartStack {
       SortedDictionary<Action, EnumResult> actions; 

       void push(Action a, EnumResult r) {
         actions.add(a, r);
       }

       void execute() {
         Stack<Action> completedActions = new Stack<Action>();
         foreach(KeyValuePair<Action, EnumResult> pair in actions) {
             try {
               pair.Key.Invoke();
               completedActions.push(pair.Key);
             }
             catch(Exception ex) {
               for (Action a in completedActions) {
                   Undo(a);
               }
               return pair.Value;
             }
         }
       }
    }

Context

StackExchange Code Review Q#42920, answer score: 8

Revisions (0)

No revisions yet.