주요글: 도커 시작하기
반응형
웹 어플리케이션의 흐름 제어를 위한 인터페이스를 정의해본다.

흐름 제어를 위한 ControlBox 인터페이스

지금까지 2회에 걸쳐서 모델 2 구조를 이용한 '로직과 프리젠테이션의 분리' 및 모델 2 구조에 커맨드 패턴을 적용하는 것에 대해서 살펴보았다. 물론, 이 두가지 요소를 통해서 우리는 모델 2 구조를 상당부분 객체 지향적으로 구현할 수 있게 되었지만 여기에 추가적으로 우리가 해야 할 부분이 있다. 바로 흐름제어를 위한 별도의 방안을 강구하는 것이다.

2부에서는 커맨드 패턴에서 사용되는 각 커맨드 처리 객체가 다음에 보여줄 JSP 페이지를 처리의 결과값을 리턴하는 방식을 사용하여 웹 어플리케이션의 흐름을 제어했었다. 물론, 이처럼 커맨드 패턴의 각 객체가 흐름제어를 하는 방식만으로 충분할 수도 있다. 하지만 커맨드 패턴에서 사용되는 객체가 아닌 별도의 객체에서 흐름 제어를 할 수 있다면, 즉 흐름 제어의 역할을 담당하는 객체를 별도로 사용한다면, 웹 어플리케이션은 더욱 객체 지향적인 구조를 갖게 될 것이다. 이번 3부에서는 흐름 제어를 위한 인터페이스를 작성해봄으로써 모델 2 구조에 대한 애기를 끝맺도록 하겠다.

처음 흐름 제어를 위한 인터페이스를 생각했을 때 생각났던 것이 JDBC API 였다. JDBC의 java.sql.ResultSet 인터페이스를 보면 next(), absolute() 등 결과 집합 행을 이동할 때 사용되는 메소드가 존재하는 것을 알 수 있으며, 이를 통해 읽어올 행의 순서를 제어한다는 사실도 알수 있다. 필자는 이러한 ResultSet 인터페이스와 비슷한 방법으로 웹 어플리케이션의 흐름 제어를 위한 인터페이스를 설계해보았으며, 그 인터페이스는 다음과 같다. (주석문은 소스 코드를 쉽게 볼 수 있도록 하기 위해서 삭제하였다.)

  package org.jcore.webapp.control.spi;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import org.jcore.webapp.control.exception.ControlBoxException;
  
  public interface ControlBox {
     
     public void setHttpInfo(HttpServletRequest request,
                             HttpServletResponse response);
     
     public int getPreviousStep();
     public int getNextStep();
     public int getCurrentStep();
     public int getRequestStep();
     public boolean isValidStep();
     public boolean isFirstStep();
     public boolean isLastStep();
     public void goNextStep() throws ControlBoxException;
     public void goPreviousStep() throws ControlBoxException;
     public void goFirstStep() throws ControlBoxException;
     public void goLastStep() throws ControlBoxException;
     public void goInvalidStep() throws ControlBoxException;
     public void clearControlBox() throws ControlBoxException;
  }

ControlBox 인터페이스를 보면 getXXX() 메소드와 isXXX() 메소드 그리고 goXXX() 메소드의 세 가지 종류의 메소드가 존재하는 것을 알 수 있다. 이러한 메소드들이 무엇을 의미하지 살펴보기 전에 ControlBox 인터페이스가 어떤 의미로 설계되었는지 이해해야만 한다. 필자는 ControlBox를 설계할 때 스텝이라는 개념을 도입하였다. 여기서 스텝은 어떤 일을 처리하는 단계를 의미하는 것이다. 스텝의 의미를 좀더 구체적으로 정의하기 위해서 다음 그림을 살펴보자.


위 그림은 사이트 가입 절차의 일부를 표시한 것으로서 유저는 일반적으로 사이트에 가입신청을 하면 약관을 보게 되고, 이후 회원 정보 입력과 확인 절차 후 최종적으로 사이트의 회원으로 가입하게 된다. 이러한 과정은 크게 두 개의 부분으로 나뉜다. 하나는 위 그림에서 윗줄에 표시된 어떤 행위를 요청하는 부분이고 다른 하나는 위 그림에서 아랫줄에 표시된 요청을 처리하는 부분이다. 여기서 각각의 요청을 하나의 스텝으로 볼 수도 있고 또는 요청의 처리를 하나의 스텝으로 볼 수도 있다. 각각의 요청을 하나의 스텝으로 볼 때, 위 그림에서 "약관보기요청", "화면1 요청", "입력확인 요청" 등이 하나의 스텝이 된다.

그렇다면 흐름 제어는 무엇인가? 위 그림에서 흐름은 "사용자가 일정한 순서로 스텝을 거치는 것"이라고 정의할 수 있으며, 따라서 흐름 제어는 "사용자가 거치게 될 스텝의 순서를 제어해주는 것"으로 정의할 수 있다. 스텝의 순서를 제어하는 것에는 다음과 같은 것이 있을 수 있다.

  • 다음 스텝으로 이동
  • 이전 스텝으로 이동
  • 처음 스텝으로 이동
  • 마지막 스텝으로 이동
ControlBox 인터페이스에 선언되어 있는 goNextStep(), goPreviousStep(), goFirstStep(), goLastStep() 메소드가 바로 이러한 스텝의 이동을 위한 것들이다. 그리고 ControlBox 인터페이스는 현재 스텝이 무엇이고 사용자가 요청한 스텝이 무엇인지 그리고 요청한 스텝은 흐름에서 올바른 지의 여부를 판단하기 위한 메소드를 정의하고 있는 데, 이들 메소드는 다음과 같다.

  • getCurrentStep() - 현재 처리할(또는 처리하는) 스텝을 구한다.
  • getRequestStep() - 요청한 스텝을 구한다.
  • goInvalidStep() - 잘못된 스텝을 요청할 경우 에러 페이지로 이동한다.
  • clearControlBox() - ControlBox와 관련해서 저장된 정보를 삭제한다.
  • isValidStep() - 올바른 스텝인지 판단한다.
  • isFirstStep() - 첫번째 스텝인지 판단한다.
  • isLastStep() - 마지막 스텝인지 판단한다.
그리고 ControlBox 인터페이스는 웹 어플리케이션의 요청/응답과 관련된 객체를 전달받기 위한 메소드인 setHttpInfo()를 선언하고 있다. ControlBox 인터페이스의 기본적인 사용 방법은 다음과 같다.

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
                    throws IOException, ServletException {
      
      // ControlBoxImpl은 ControlBox 인터페이스를 구현한 클래스
      ControlBox box = new ControlBoxImpl();
      
      box.setHttpInfo(request, response);
      if (box.isValidStep()) {
        // 어떤 알맞은 처리를 한다.
        ...
        box.goNext();
      } else {
        // 알맞은 스텝이 아니면,
        box.goInvalidStep();
      }
  }

위 코드를 보면, 웹 어플리케이션의 흐름 제어는 ControlBox 인터페이스에 정의되어 있는 메소드를 통해서 처리되기 때문에 서블릿이나 기타 로직을 처리하는 객체에서 흐름을 제어하기 위한 코드를 삽입할 필요가 없음을 알 수 있다. 흐름 제어에 있어서 중요한 것은 ControlBox 인터페이스를 구현하는 것 뿐이다. 실제 ControlBox 인터페이스를 구현한 예제는 뒤에서 살펴볼 것이다.

ControlBox를 생성해주는 ControlBoxFactory

앞의 코드는 ControlBox 인터페이스를 구현한 ControlBoxImpl 이라는 클래스의 생성자를 사용하여 직접적으로 ControlBox를 생성하는 형태를 취하고 있다. 물론, ControlBoxImpl 을 직접 생성하는 것이 나쁜 것은 아니지만 이럴 경우 생성할 ControlBox의 타입을 ControlBoxImpl2로 변경하려면 소스 코드에서 직접 변경해주어야 하는 단점이 존재한다.

이러한 단점을 보완하기 위한 방법으로는 우리는 ControlBox를 생성하는 역할을 갖는 ControlBox 팩토리를 생각해보았다. ControlBoxFactory는 ControlBox를 생성해주는 팩토리 클래스들이 구현해야 하는 추상 클래스로서 다음과 같은 기능을 제공하는 것을 그 목적으로 하고 있다.

  • 런타임에 알맞은 ControlBox를 생성해주는 팩토리를 구한다.
  • 팩토리 클래스가 구현해야 할 메소드를 정의한다.
런타임에 알맞은 팩토리 클래스의 인스턴스를 구하는 것은 Properties 객체를 ControlBoxFactory.getInstance(Properties) 메소드에 전달해주는 방식을 사용하여 해결하였으며, 팩토리 클래스의 생성과 관련된 메소드와 필드는 모두 클래스 필드(또는 static 필드)로 처리하였다. 다음은 ControlBoxFactory 추상 클래스의 소스 코드이다. ControlBox 인터페이스와 마찬가지로 주석은 삭제한 채로 기재하였다.

  package org.jcore.webapp.control.spi;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import java.util.HashMap;
  import java.util.Properties;
  import org.jcore.webapp.control.exception.ControlBoxException;
  
  public abstract class ControlBoxFactory {
     
     public static final String PROPERTY_NAME = "jcore.controlbox.factory.class";
     public static final String DEFAULT_FACTORY_CLASSNAME = 
           "org.jcore.webapp.control.impl.DefaultControlBoxFactory";
     
     public final static ControlBoxFactory getDefaultInstance()
 
            throws ControlBoxException {
        return ControlBoxFactory.getFactoryInstance(
              System.getProperties() );
     }
     
     public final static ControlBoxFactory getInstance(Properties prop)
 
            throws ControlBoxException {
        return ControlBoxFactory.getFactoryInstance( prop );
     }
     
     private static ControlBoxFactory getFactoryInstance(Properties prop) 
             throws ControlBoxException {
        if (prop == null) prop = System.getProperties();
        
        String className = prop.getProperty(PROPERTY_NAME,
                                            DEFAULT_FACTORY_CLASSNAME);
        String usingClass = null;
        if (className == null) usingClass = DEFAULT_FACTORY_CLASSNAME;
        else usingClass = className;
        
        if (factories.containsKey(usingClass)) {
           return (ControlBoxFactory)factories.get(usingClass);
        }
        
        try {
           Class factoryClass = Class.forName(usingClass);
           Object factoryInstance = factoryClass.newInstance();
           if ( !(factoryInstance instanceof ControlBoxFactory) ) {
              throw new ControlBoxException(className +
                         " is not ControlBoxFactory!");
           } else {
              ((ControlBoxFactory)factoryInstance).setProperties(prop);
              factories.put(usingClass, factoryInstance);
              return (ControlBoxFactory)factoryInstance;
           }
        } catch(ClassNotFoundException ex) {
           throw new ControlBoxException(ex);
        } catch(InstantiationException ex) {
           throw new ControlBoxException(ex);
        } catch(IllegalAccessException ex) {
           throw new ControlBoxException(ex);
        }
     }
     
     private static HashMap factories = new HashMap();
     
     public abstract ControlBox getControlBox(HttpServletRequest request, 
                                             HttpServletResponse response)
 
            throws ControlBoxException;     
     public abstract void setProperties(Properties prop);
  }

ControlBoxFactory 추상 클래스에서 중요한 메소드는 실제로 팩토리 클래스의 인스턴스를 생성해주는 getFactoryInstance(Properties) 메소드이다. getFactoryInstance(Properties) 메소드는 다음과 같은 절차로 팩토리 클래스의 인스턴스를 생성한다.

  1. 파라미터로 전달받은 Properties로부터 생성할 팩토리 클래스의 이름을 구한다.
  2. 클래스의 이름을 사용하여 팩토리 클래스가 이미 생성되어 factories 해시맵에 저장되어 있는 지 살펴본다.
    1. factories 해시맵에 저장되어 있을 경우, 저장되어 있는 인스턴스를 리턴한다.
  3. 저장되어 있지 않을 경우
    1. 클래스의 이름을 사용하여 인스턴스를 생성한다.
    2. 인스턴스의 타입이 ControlBoxFactory 인지 검사한다.
    3. 인스턴스의 setProperties() 메소드를 호출하여 Properties 객체를 전달한다.
    4. factories 해시맵에 클래스이름을 키로 사용하여 인스턴스를 저장한다.
    5. 인스턴스를 리턴한다.
getDefaultInstance() 메소드와 getInstance(Properties) 메소드는 각각 시스템 프로퍼티와 파라미터로 전달받은 프로퍼티를 사용하여 팩토리 인스턴스를 생성한다. 이때 Properties 객체는 생성할 클래스의 이름을 "jcore.controlbox.factory.class" 프로퍼티에 저장하고 있어야 한다. 만약 "jcore.controlbox.factory.class" 프로퍼티를 갖고 있지 않을 경우에는 ControlBoxFactory.DEFAULT_FACTORY_CLASSNAME 상수값을 클래스 이름으로 사용한다.

예를 들어, ControlBox를 생성하는 팩토리 클래스의 이름이 "com.javacan.control.CBFactory"라고 할 경우 다음과 같은 방법으로 ControlBoxFactory를 생성하면 된다.

  
  System.setProperties(ControlBoxFactory.PROPERTY_NAME,
                       "com.javacan.control.CBFactory");
  
  ControlBoxFactory cbFactory = ControlFactory.getDefaultInstance();

  // Properties 클래스를 사용할 경우
  // Properties prop = new Properties();
  // prop.setProperties(ControlBoxFactory.PROPERTY_NAME,
  //                    "com.javacan.control.CBFactory");
  // ControlBoxFactory cbFactory = ControlFactory.getInstance(prop);

ControlBoxFactory 추상 클래스를 상속 받은 팩토리 클래스들은 두 개의 추상 메소드인 getControlBox(HttpServletRequest, HttpServletResponse)와 setProperties(Properties)를 알맞게 구현하면 된다. 이 중 getControlBox() 메소드는 클라이언트의 요청/응답과 관련된 ControlBox 객체를 생성해주는 역할을 하고, setProperties() 메소드는 ControlBoxFactory가 ControlBox를 생성할 때 필요로 하는 프로퍼티를 전달할 때 사용된다. getControlBox() 메소드는 내부적으로 리턴할 ControlBox의 setHttpInfo() 메소드를 호출해야 한다.

종합적으로 ControlBoxFactory와 ControlBox를 사용하여 웹 어플리케이션을 흐름을 제어하도록 구현한 서블릿 클래스는 다음과 같은 기본 골격을 갖게 된다.

  public class SomeServlet extends .. {
     
     private ControlBoxFactory cbFactory;
     
     public void init() throws ServletException {
        try {
           System.setProperty(ControlBoxFactory.PROPERTY_NAME,
                           "some.className");
           // 기타 필요한 프로퍼티 지정
        
           cbFactory = ControlBoxFactory.getDefaultInstance();
        } catch(ControlBoxException ex) {
           // 에외 처리
        }
     }
     
     public void doGet(..) throws .. {
        try {
           ControlBox control = cbFactory.getControlBox(request, response);
           if (control.isValidStep()) {
              // 스텝에 따른 알맞은 처리
              // -> 이 부분에서 커맨드 패턴을 적용할 수 있다.
              ...
              control.goNext();
           } else {
              control.goInvalidStep();
           }
        } catch(ControlBoxException ex) {
           // 에외 처리
        }
     }
  }



구현 예제

이 글에서는 총 5 단계를 갖는 웹 어플리케이션의 흐름 제어를 위한 클래스를 작성해보도록 하자. 가장 먼저 구현해야 하는 것은 흐름 제어를 해 주기 위해 ControlBox 인터페이스를 구현한 DefaultControlBox 클래스이다. 이 클래스는 다음과 같은 특징을 갖고 있다.

  1. 각 단계별로 처리 결과를 보여줄 JSP 페이지를 지정할 수 있다.
    • jcore.controlbox.default.page1 - page5 까지의 프로퍼티를 사용하여 각 단계별 처리 결과 JSP 페이지를 지정할 수 있다.
    • 단계별 JSP 페이지를 지정하지 않을 경우 기본 JSP 페이지를 보여준다.

  2. DefaultControlBoxFactory 클래스를 통해서 생성된다.
    • DefaultControlBoxFactory은 세션에 DefaultControlBox 인스턴스를 저장한다.

  3. 스텝 정보를 정수형 값으로 저장하며, goNextStep() 메소드를 통해서 1 씩 증가한다.
다음은 DefaultControlBox 클래스의 소스 코드이다.

  package org.jcore.webapp.control.impl;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.servlet.http.HttpSession;
  import javax.servlet.RequestDispatcher;
  import javax.servlet.ServletException;
  import java.io.IOException;
  import java.util.Properties;
  
  import org.jcore.webapp.control.spi.ControlBox;
  import org.jcore.webapp.control.exception.ControlBoxException;
  import org.jcore.webapp.control.exception.InvalidGoStepException;
  
  public class DefaultControlBox implements ControlBox {
     
     public static final String DEFAULT_STEP1_PAGE = "/step/step1result.jsp";
     public static final String DEFAULT_STEP2_PAGE = "/step/step2result.jsp";
     public static final String DEFAULT_STEP3_PAGE = "/step/step3result.jsp";
     public static final String DEFAULT_STEP4_PAGE = "/step/step4result.jsp";
     public static final String DEFAULT_STEP5_PAGE = "/step/step5result.jsp";
     public static final String DEFAULT_INVALID_STEP_PAGE = "/step/invalidStep.jsp";
     
     public static final int DEFAULT_STEP_COUNT = 5;
     
     private HttpServletRequest request;
     private HttpServletResponse response;
     
     private int firstStep = 1;
     private int lastStep = DEFAULT_STEP_COUNT;
     
     private int currentStep = 1;
     private int requestStep = 0;
     
     private String step1_resultPage;
     private String step2_resultPage;
     private String step3_resultPage;
     private String step4_resultPage;
     private String step5_resultPage;
     private String invaldStep_page;
     
     private String[] stepPages;
     
     public DefaultControlBox(Properties prop) {
        // 프로퍼티로부터 각 단계의 결과를 보여줄 페이지 URI를 읽어온다.
        if (prop == null) {
           setDefaultResultPage();
        } else {
           step1_resultPage = prop.getProperty("jcore.controlbox.default.page1",
                                                 DEFAULT_STEP1_PAGE);
           step2_resultPage = prop.getProperty("jcore.controlbox.default.page2",
                                                 DEFAULT_STEP2_PAGE);
           step3_resultPage = prop.getProperty("jcore.controlbox.default.page3",
                                                 DEFAULT_STEP3_PAGE);
           step4_resultPage = prop.getProperty("jcore.controlbox.default.page4",
                                                 DEFAULT_STEP4_PAGE);
           step5_resultPage = prop.getProperty("jcore.controlbox.default.page5",
                                                 DEFAULT_STEP5_PAGE);
           invaldStep_page  = prop.getProperty("jcore.controlbox.default.invalidPage",
                                                 DEFAULT_INVALID_STEP_PAGE);
        }
        createStepPages();
     }
     
     private void setDefaultResultPage() {
        step1_resultPage = DEFAULT_STEP1_PAGE;
        step2_resultPage = DEFAULT_STEP2_PAGE;
        step3_resultPage = DEFAULT_STEP3_PAGE;
        step4_resultPage = DEFAULT_STEP4_PAGE;
        step5_resultPage = DEFAULT_STEP5_PAGE;
        invaldStep_page  = DEFAULT_INVALID_STEP_PAGE;
     }
     private void createStepPages() {
        stepPages = new String[6];
        stepPages[0] = invaldStep_page;
        stepPages[1] = step1_resultPage;
        stepPages[2] = step2_resultPage;
        stepPages[3] = step3_resultPage;
        stepPages[4] = step4_resultPage;
        stepPages[5] = step5_resultPage;
     }
     public void setHttpInfo(HttpServletRequest request,
                             HttpServletResponse response) {
        this.request = request;
        this.response = response;
        
        requestStep = currentStep;
     }
     
     public int getPreviousStep() {
        if (currentStep > 1) return currentStep - 1;
        else return -1;
     }
     
     public int getNextStep() {
        if (currentStep < lastStep ) return currentStep + 1;
        else return -1;
     }
     
     public int getCurrentStep() {
        return currentStep;
     }
     
     public int getRequestStep() {
        return requestStep;
     }
     
     public boolean isValidStep() {
        return requestStep == currentStep;
     }
     
     public boolean isFirstStep() {
        return currentStep == firstStep;
     }
     public boolean isLastStep() {
        return currentStep == lastStep;
     }
     public void goNextStep() throws ControlBoxException {
        if (currentStep > lastStep)
           throw new InvalidGoStepException(
           "Can't forward next step "+
           "because you process aleady last step!");
        try {
           forwardPage(currentStep ++);
        } catch(ControlBoxException ex) {
           currentStep --;
           throw ex;
        }
     }
     
     public void goPreviousStep() throws ControlBoxException {
        if (currentStep == lastStep)
           throw new InvalidGoStepException(
           "Can't forward previous step " +
           "because you are going to process first step!");
        try {
           forwardPage(currentStep --);
        } catch(ControlBoxException ex) {
           currentStep ++;
           throw ex;
        }
     }
     public void goFirstStep() throws ControlBoxException {
        int temp = 0;
        try {
           temp = currentStep;
           currentStep = firstStep;
           forwardPage(currentStep);
        } catch(ControlBoxException ex) {
           currentStep = temp;
           throw ex;
        }
     }
     public void goLastStep() throws ControlBoxException {
        int temp = 0;
        try {
           temp = currentStep;
           currentStep = lastStep;
           forwardPage(currentStep);
        } catch(ControlBoxException ex) {
           currentStep = temp;
           throw ex;
        }
     }
     public void goInvalidStep() throws ControlBoxException {
        forwardPage(0);
     }
     public void clearControlBox() throws ControlBoxException {
        HttpSession session = request.getSession();
        session.removeAttribute(
                DefaultControlBoxFactory.SESSION_ATTRIBUTE_NAME);
     }
     
     private void forwardPage(int step) throws ControlBoxException {
        try {
           RequestDispatcher rd = request.getRequestDispatcher(stepPages[step]);
           rd.forward(request, response);
        } catch(Exception ex) {
           throw new ControlBoxException(ex);
        }
     }   
  }

DefaultControlBox 클래스에서 눈여겨 봐야 할 부분은 현재 스텝 정보를 저장하고 있는 currentStep 필드와 사용자가 요청한 스텝 정보를 저장하는 requestStep 필드를 어떻게 관리하는가이다. 소스 코드 자체는 복잡하지 않으므로 여러분 스스로 분석해보기 바란다.

이제 DefaultControlBox 클래스의 인스턴스를 생성해주는 DefaultControlBoxFactory 클래스를 살펴보자. 이 클래스는 팩토리 클래스이므로 ControlBoxFactory 추상 클래스를 상속받으며, 세션으로부터 DefaultControlBox 인스턴스를 구하거나 또는 존재하지 않을 경우 새롭게 DefaultControlBox 인스턴스를 생성해서 세션에 저장하는 역할을 한다. DefaultControlBox 인스턴스를 세션에 저장할 때 사용되는 이름은 "org.jcore.webapp.control.box"이다. 다음은 DefaultControlBoxFactory 클래스의 소스 코드이다.

  package org.jcore.webapp.control.impl;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.servlet.http.HttpSession;
  import java.util.Properties;
  
  import org.jcore.webapp.control.exception.ControlBoxException;
  import org.jcore.webapp.control.spi.ControlBoxFactory;
  import org.jcore.webapp.control.spi.ControlBox;
  
  public class DefaultControlBoxFactory extends ControlBoxFactory {
     
     public static final String SESSION_ATTRIBUTE_NAME = 
           "org.jcore.webapp.control.box";
     
     private Properties prop;
     
     public DefaultControlBoxFactory() {
        // do nothing
     }
     
     public ControlBox getControlBox(HttpServletRequest request,
                                     HttpServletResponse response) {
        HttpSession session = request.getSession();
        ControlBox box = (ControlBox)session.getAttribute(SESSION_ATTRIBUTE_NAME);
        if (box == null) {
           box = new DefaultControlBox(prop);
           session.setAttribute(SESSION_ATTRIBUTE_NAME, box);
        }
        box.setHttpInfo(request, response);        
        return box;
     }
     
     public void setProperties(Properties prop) {
        this.prop = prop;
     }
  }

이제 마지막으로 DefaultControlBox를 사용하여 흐름 제어를 하는 서블릿을 살펴보자. 이 서블릿은 단순히 테스트를 위한 것이긴 하지만 ControlBox를 사용함으로써 흐름 제어와 관련된 많은 부분을 서블릿 코드에서 삭제할 수 있다는 것을 알게 될 것이다. 다음 서블릿 코드에서 여러분이 눈여겨 봐야 할 것은 어떤 식으로 ControlBoxFactory를 생성하고 또 어떤 식으로 ControlBox를 사용하여 흐름 제어를 하는가 하는 부분이다.

  import javax.servlet.*;
  import javax.servlet.http.*;
  
  import org.jcore.webapp.control.spi.ControlBoxFactory;
  import org.jcore.webapp.control.spi.ControlBox;
  import org.jcore.webapp.control.exception.ControlBoxException;
  import java.util.Properties;
  import java.io.IOException;
  
  public class MemberRegistryServlet extends HttpServlet {
     
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
                 throws IOException, ServletException {
        processRegistry(request, response);
     }
     
     public void doPost(HttpServletRequest request,
                         HttpServletResponse response)
                  throws IOException, ServletException {
        processRegistry(request, response);
     }
     
     public void init() throws ServletException {
        Properties prop = new Properties();
        prop.setProperty("jcore.controlbox.default.page1", "/registry/term.jsp");
        prop.setProperty("jcore.controlbox.default.page2", "/registry/inputform1.jsp");
        prop.setProperty("jcore.controlbox.default.page3", "/registry/inputform2.jsp");
        prop.setProperty("jcore.controlbox.default.page4", "/registry/confirm.jsp");
        prop.setProperty("jcore.controlbox.default.page5", "/registry/complete.jsp");
        prop.setProperty("jcore.controlbox.default.invalidPage", "/registry/invalid.jsp");
        try {
           cbFactory = ControlBoxFactory.getInstance(prop);
        } catch(ControlBoxException ex) {
           throw new ServletException(ex);
        }
     }
     
     private ControlBoxFactory cbFactory;     
     private void processRegistry(HttpServletRequest request,
                                  HttpServletResponse response)
                  throws IOException, ServletException {
        try {
           ControlBox control = cbFactory.getControlBox(request, response);
           if (control.isValidStep()) {
              switch(control.getRequestStep()) {
                 case 1: // 약관 보기 요청 처리
                         break;
                 case 2: // 폼1 보기 요청 처리
                         break;
                 case 3: // 폼1 입력 데이터 처리 및
                         // 폼2 보기 요청 처리
                         break;
                 case 4: // 폼2 입력 데이터 처리 및
                         // 확인 화면 보기 요청 처리
                         break;
                 case 5: // 확인 요청 처리 및
                         // 가입 완료 화면 보기 요청 처리
              }
              control.goNextStep();
           } else {
              control.goInvalidStep();
           }
        } catch(ControlBoxException ex) {
           throw new ServletException(ex);
        }
     }
  }

위 코드에서 switch 부분은 커맨드 패턴으로 대체할 수도 있을 것이다.

결론

이번 3부에서는 ControlBox를 이용하여 웹 어플리케이션의 흐름을 제어하는 것에 대해서 살펴보았다. 여러분은 ControlBox 인터페이스와 ControlBoxFactory 추상 클래스를 상속하여 알맞게 구현함으로써 서블릿(또는 JSP도 될 수있다)으로부터 상당량의 흐름 제어 코드를 삭제할 수 있게 되었다. 이는 모델 2 구조를 더욱 더 (역할 중심의) 객체 지향적으로 구현할 수 있도록 해 준다.

지금까지 3회에 걸쳐서 모델 2 구조의 기본 구현 방법과 클라이언트의 요청 처리를 위한 커맨드 패턴, 그리고 웹 어플리케이션의 흐름 제어를 위한 ControlBox 모델에 대해서 살펴보았다. 이를 통해서 여러분은 모델 2 구조가 무엇이며 더 나아가 모델 2 구조를 어떤 식으로 구현해야 할 지에 대한 기초 지식을 쌓았을 것이다. 여러분이 이러한 기초 지식을 토대로 웹 어플리케이션을 객체 지향적으로 개발할 수 있기를 바라며 이 시리즈를 마친다.

+ Recent posts