0

I have a Task in my project , to complete that i have call(REST) multiple external systems. If my call fails at some level , i have to rollback all my previous calls(making call with undo action) .

Looking for effective way of implementing it in JAVA. Can Someone point me to any design pattern if exists?

    public void myTask(){


        call subtask1() ; //external sys

        try{
            call subtask2();
        }
        catch(Exception){
            call undo_subtask1();
        }

        try{
            call subtask3();
        }
        catch(Exception){
            call undo_subtask1();
            call undo_subtask2();  //calling in any order is fine.
        }

        .....
    }

The above code is similar to database rollback. Tomorrow there can be subtask 4, 5,6 . I don't want to add try/catch again. Is there recommended way to achieve this?

When i googled i got command pattern. But i don't understand how that will help my case.

Thanks

4
  • Would it be problematic if you try to undo an action that failed or never got executed? Feb 24, 2020 at 11:18
  • I don't see any problem other than wasting a network call.
    – Jeevi
    Feb 24, 2020 at 11:27
  • Well, represent your (sub)tasks as objects using the Command pattern - encode both the execute() and undo() operations in each object (your code already has paired methods). Then pass in a list of commands to myTask (or make a proper object and store the list as a field, pass to the constructor). Execute one by one; if a command succeeds, go on to the next one, if it fails, go back and call undo() until you reach the first one (reverse the loop, or use a stack, or something), and then exit the method. Then, when you need to add more subtasks, just add new command objects to the list. Feb 24, 2020 at 12:49
  • ^ This is under the assumption that you don't want to change the myTask method when new tasks need to be implemented, since you've said "Tomorrow there can be subtask 4, 5,6 . I don't want to add try/catch again." Feb 24, 2020 at 12:50

2 Answers 2

3

If it is not a problem to run the undo action also for the steps that either failed or weren't executed at all, you can just use the fact that an exception will cause the execution of the try block to be stopped with a jump to the catch block.

public void myTask()
{
  try
  {
    call subtask1();
    call subtask2();
    call subtask3();
    ...
  }
  catch(Exception){
    ...
    call undo_subtask3();
    call undo_subtask2();
    call undo_subtask1();
    throw; // re-throw the exception
  }
}

Adding a new subtask is just adding the corresponding call subtaskX()/call undo_subtaskX() to the try and catch blocks.

If you want to avoid calling undo actions for subtasks that didn't complete successfully, you could either use nested try blocks, or a set of booleans to indicate which subtasks completed successfully and need to be rolled back in case of an exception.

2

We can wrap each pair undo_subtask1, undo_subtask2 etc, in an object

interface Subtask {
    void undo();
}

We can then put each task into a container, and the cleanup calls all the undos

public void myTask() {
  try (List<Subtask> done = new ArrayList<Subtask>())
  {
    subtask1();
    done.add(new Subtask {
      public void undo() { undo_subtask1(); }
    })

    subtask2();
    done.add(new Subtask {
      public void undo() { undo_subtask2(); }
    })

    subtask3();
    done.add(new Subtask {
      public void undo() { undo_subtask3(); }
    })

    ...
  }
  catch(Exception){
    for (Subtask subtask : done)
    {
      subtask.undo();
    }

    throw;
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.