디자인패턴에서 일반적으로 Rollback 구현을 위해서 memento 패턴을 소개하고 있습니다만,
일반적으로 Job이나 Work를 생성하는 유형의 Application의 구현에서,

Rollback을 기능을 구현하는 것이 그다지 쉬운 구현은 아니다. (memento로 커버하기엔 복잡도가 높다)

더 정확하게 말해 특정기능을 부분부분 프로세스로 나누어 프로세스를 하나하나 순차적으로 수행을 하는 과정을 하나의 Job으로 본다면,

각 프로세스에 대한 구현을 역으로 수행하는 부분의 구현이 필요하다. 

일반적으로 특정 프로세스가 수행될때 수행의 상태 정보를 저장해두고,
사용자에 의해 Rollback 요청이 왔을때  지금까지 했던 작업 상태 정보를 역으로 수행하는 프로세스를 역순으로 가동시켜야 한다. (상당히 귀찮은 작업이다)

그림으로 나타내면 대략적으로 아래와 같은 구동흐름을 보인다.

하나의 Job이 A -> B -> C -> D와 같은 4개의 단계의 프로세스로 실행되는 흐름을 가진다고 가정했을때,
C가 실행중인 시점에 사용자로 부터 Cancel 요청이 왔을때,
각 프로세스별 Rollback 프로세스가 C' -> B' -> A' 의 순서로 실행되어야,
해당 Job의 이전 상태로 복원되는 Rollback이 최종 수행된다

하나의 Job Class내에 이러한 Rollback의 구동흐름을 구현은 상당히 복잡해 진다.
(프로세스, 롤백프로세스를 구현하고 현재 실행상태와 흐름을 관리하는 코드를 구현해야 하기 때문이다)

이러한 복잡한 구현을 다른 class로 분리하고,
프로세스의 구현에 어려운 상태정보 관리를 개선하고자 아래와 같은 개념의 디자인의 흐름을 생각하게 되었다.

프로세스를 하나의 클래스로 정의하여,
하나의 프로세스 클래스 내에서 해당 프로세스의 수행 상태를 저장하고, 해당 프로세스의 rollback code까지 포함한다.

Tree와 같은 형태로 프로세스를 구성하여,
Root로 부터 특정 프로세스를 연쇄적으로 수행하여(재귀recursive호출),
특정 시점에 사용자로 부터 cancel 요청이 왔을때,
Tree를 역으로 순회하면서 rollback 프로세스를 수행하면,
A -> B -> C -> userCancel -> C' -> B' ->A'의 흐름으로 프로세스가 진행된다.

Tree 기반의 수행 흐름을 구현하기 위해,
Chain of Responsibility 와 Template Method 디자인 패턴의 아이디어를 적절히 조합하여 하나의 프로세스를 추상화한 아래와 같은 클래스를 구현하였다.

 
import java.util.*;

public abstract class AbstractJobProcess{
 
 private static int pid = 0;
 private String strCommand = null;
 
 private AbstractJobProcess nextProcess = null;
 private volatile boolean cancelByUser = false;
 
 private ArrayList<IProcessEventListener> lstListeners = null;
 //private Vector<String> lstResultLog = null;
 
 protected AbstractJobProcess(String command)
 {
  pid++;
  strCommand = command;
 }
 
 public String getCommand()
 {
  return this.strCommand;
 }
 
 public AbstractJobProcess addNextProcess(AbstractJobProcess process)
 {
  this.nextProcess = process;    
  return this.nextProcess;
 }
   
 public void cancel()
 {
  this.cancelByUser = true;
  if(this.nextProcess != null) this.nextProcess.cancel();
 }
 
 public boolean start()
 {
  if(this.cancelByUser) return false;
  
  if(this.process())
  {
   if(this.cancelByUser){
    // excute rollback process
    this.rollback();
    return false;
   } else {
    if(this.nextProcess != null)
    {
     boolean nextJobResult = this.nextProcess.start();
     if(nextJobResult)
      return true;
     else
     {
      this.rollback();
      return false;
     }
    }
    return true;
   }
  } else {
   
   this.rollback();
   return false;
  }
 }
 
 public abstract boolean process();  // 정상 process 구현
 public abstract boolean rollback();  // rollback 구현

}


 위의 추상클래스를 상속하여 개별 프로세스를 구현할 수 있으며, 구현 클래스에서는 abstract method인 process와 rollback을 구현해야 한다. (프로세스별 동작 방식과 상태 저장 방식이 틀리기 때문이다.)

 process와 rollback외 추가적으로 프로세스의 재귀적인 호출을 위한 start() method와 수행중인 Job의 취소를 위한 cancel을 제공하며, cancel() method 또한 연결 프로세스간 재귀적인 호출이 이루어 지는 구조이다
 
 프로세스간 chain과 같은 연결구조를 설정하기 위해 addNextProcess(AbstractJobProcess process) method도 제공한다.

 이 외에 프로세스에 상태정보를 event로 전송하기 위해 Observer Pattern을 추가적으로 적용할 수도 있다.

 위의 AbstractJobProcess를 이용하여 아래 구조의 rollback을 수행하는 Job(Task)을 구현해 보자.


TempJob은 Porcess A, B, C, D 순서의 실행구조를 가진 임의의 Job Class로 job의 시작과 중단을 위한 start()와 cancelJob() method를 제공한다.

ProcessA .. D는 Console에 각 프로세스의 message를 0.3초간 지연후 출력해주는 구현이 되어있다. process의 수행중 cancel 요청이 왔을때 실행시점에 rollback 프로세스가 실행됨을 보여주기 위해 임의의 sleep을 삽입하였다.

코드는 아래와 같다. ( Process 별 구현은 동일 )

 public class ProcessA extends AbstractJobProcess{

 protected ProcessA(String command) {
  super(command);
  }

 @Override
 public boolean process() {
  try { Thread.sleep(300); } catch (InterruptedException e) {}
  
  System.out.println("A excuted by " + this.getCommand());
  return true;
 }

 @Override
 public boolean rollback() {
  System.out.println("A' excuted by " + this.getCommand());
  return true;
 }

}


A, B, C, D 4개의 Process를 연결하여 실행시키는 코드는 아래와 같다. (TempJob.java)

 import java.util.Observable;

public class TempJob extends Observable{
 
 private AbstractJobProcess rootProcess = null;
 
 public TempJob()
 {
  ;
 }
 
 public void setProcess(AbstractJobProcess buildedProcess)
 {
  this.rootProcess = buildedProcess;
 }
  
 public void startJob()
 {
  this.rootProcess.start();
 }
 
 public void cancelJob()
 {
  System.out.println("[LOG] Request to cancel..");
  if(this.rootProcess != null)
   this.rootProcess.cancel();
 }
 
 public static void main(String...v)
 {
  // A, B, C, D 연결된 Process를 생성한다
  AbstractJobProcess rootProc = new ProcessA("procA");
  {
   rootProc.addNextProcess(new ProcessB("procB"))
    .addNextProcess(new ProcessC("procC"))
    .addNextProcess(new ProcessD("procD"));
  }
  
  final TempJob job = new TempJob();
  job.setProcess(rootProc);

  // 생성된 Process를 실행한다.
  job.startJob();
 }
 
}


chain의 형태로 아래와 같이 프로세스를 생성후 연결 시켰다
AbstractJobProcess rootProc = new ProcessA("procA");
  {
   rootProc.addNextProcess(new ProcessB("procB"))
    .addNextProcess(new ProcessC("procC"))
    .addNextProcess(new ProcessD("procD"));
  } 


해당 프로그램을 실행하면 아래와 같이 기대한 A->B->C->D의 연속적인 실행이 이루어 지는 것을 확인 할 수 있다.



Process가 실행중에 사용자에 의한 Cancel 요청시 Process의 rollback이 이루어 지는것을 확인하기 위해서,
main method 내에 아래의 code를 삽입하였다.

 public static void main(String...v)
 {
  // build proc
  AbstractJobProcess rootProc = new ProcessA("procA");
  {
   rootProc.addNextProcess(new ProcessB("procB"))
    .addNextProcess(new ProcessC("procC"))
    .addNextProcess(new ProcessD("procD"));
  }
  
  final TempJob job = new TempJob();
  job.setProcess(rootProc);
  
  // cancel job
  new Thread() {
   public void run() {
    try { Thread.sleep(800); } catch (InterruptedException e) {}
    job.cancelJob();
   }
  }.start();

  
  job.startJob();
  
 }

쓰레드는 실행 0.8초후 job의 cancelJob() method를 호출한다.
프로세스별 0.3 초간의 sleep이 있으므로 cancelJob()이 호출되는 시점은 대략적으로 ProcessC가 실행되는 시점이다.

위 코드를 실행하면 기대했던 A->B->C->userCancel->C'->B'->A'의 실행흐름을 확인 할 수 있다.



C가 실행중 cancel 요청이 왔으므로 C의 process() method가 최종 return을 한후 재귀적인 rollback이 수행된다.

정리 :
필자가 프로그램 구현중 그냥 생각해본 디자인 방식(? 패턴?) 이다. 
Template method, Chain of resposibility의 아이디어로 적절히 조합한 형태이다.
앞서 언급한 바와 같이 UI 와 연결된 프로그램이라면 Observer pattern등을 적용하여 Job의 정교한 진행상태를 확인 할 수 있을 것으로도 생각되며,
Interrupt 기반의 cancel이 아니기에 수행시간이 긴 Process가 실행중 User Cancel이 왔을때 rollback 처리가 즉각적이지 못하다는 단점도 있으나,
여러 사람이 작업하는 프로젝트에서 손쉽게 프로세스를 구현하여 추가, 삭제가 가능하다는 장점이 있다.

더 좋은 아이디어가 있다면 알려주면 감사하겠다.. ^^ 

김영곤 (gonni21c@gmail)
 

+ Recent posts