기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

MVC 패턴의 구현 원리

기술자료
DBMS별 분류
Etc
작성자
dataonair
작성일
2012-12-28 00:00
조회
10540



자바카페와 함께하는 웹 기초 강좌

MVC 패턴의 구현 원리



지금까지 진행된 내용을 잘 학습했다면 이제 간단한 웹 애플리케이션을 작성할 수 있을 것이다. 하지만 더욱 완성도 있는 애플리케이션 작성을 위해서는 다양한 개발 기법을 익혀 두는 게 좋다. 이번 시간에는 완성도 높은 웹 애플리케이션 개발을 위해 많이 알려져 있고, 대부분의 프로젝트 현장에서 사용되는 MVC 패턴에 대해 알아본다.



애플리케이션을 개발할 때 MODEL - VIEW - CONTROL LER 등으로 모듈을 분리하면 효율적으로 애플리케이션을 개발하고 유지보수할 수 있다. 최근 대부분의 프로젝트에서는 스트럿츠나 스프링 MVC 등 MVC 프레임워크가 이용되고 있는데 이번 시간에는 MVC 프레임워크의 기본이 되는 MVC 패턴에 대한 기초 지식과 구현 방법에 대해 설명한다.



유지보수와 모듈화

소프트웨어 개발에서 중요한 관심사 중 하나는 다양한 이슈를 한 곳에서 다뤄서는 안 된다는 것이다(하나의 메소드에 너무 많은 기능을 넣거나, 하나의 클래스에 너무 많은 책임을 두거나, 비즈니스 로직에 기술적 이슈를 직접 다루지 않게 하는 등). 그래서 많은 이들이 간편한 유지보수를 위한 효율적인 모듈화에 대해 고민한다. 유지보수는 특정 기간에만 필요한 게 아니다. 개발 중에도 지속적인 관리와 수정이 필요해 유지보수가 가능한 소스를 만드는 것은 굉장히 중요한 요소로 꼽힌다. 단순하고 확장할 필요도 없는 심플한 케이스에는 지난 시간에 배운 내용만으로도 애플리케이션 제작에 문제가 없다. 하지만 실무의 다양한 프로젝트에서는 그렇게 간단한 문제만을 다루지 않는다.

지난 달 ‘JSP 활용하기’의 예제는 모든 로직이 하나의 JSP 파일에 뭉쳐 있었다. DB 연계 작업도 한 곳에서 처리했고, 외부 요청 부분과 화면을 출력하는 HTML 등 모든 로직이 한 곳에서 이뤄졌다. 조회, 수정, 삭제 등 각 파일마다 기능별로 동일한 로직이 수행되는 경우도 존재했다. 중앙 통제도 되지 않으며, 흐름을 제어할 수도 없었다. 또한 각 파일에서 각자가 트랜잭션 관리 및 로그 관리 등 기술적 이슈와 비즈니스에 대한 로직 처리 그리고 화면에 표현하는 출력과 관련된 모든 부분을 처리해야 했다. 이 모든 것들이 개별적으로 이뤄졌는데 그중 가장 문제되는 부분은 비즈니스와 프레젠테이션이 함께 있던 부분이다.

필자가 진행했던 과거의 프로젝트를 예로 들어보자. 필자는 RIA 플랫폼 환경에서 UI 기능을 구현하고 있었다. 어느 날 동일한 화면을 메일로 전송할 일이 생겨서 HTML(JSP)로 UI를 바꿔야 했다. 다행히 비즈니스와 UI를 별도로 처리하고 있었기 때문에 UI만 변경해 문제를 해결할 수 있었다. 하지만 비즈니스가 UI에 함께 있었다면 어땠을까 기존 소스에서 비즈니스를 꺼내고 이를 다른 모듈에 넣고 태그와 스크립트를 그 사이사이에 적당히 집어넣는 등 복잡한 작업을 중복적으로 수행해야 했을 것이다.

앞서 모듈화되지 않은 소스에 대해 언급하며, 일관성 있는 제어가 불가능하다는 점과 비즈니스 및 UI 등 여러 부분이 하나의 파일에 담겨 있다는 점을 언급했다. 이제부터 학습할 MVC 패턴은 이런 점에서 모듈화에 도움을 줄 것이다. 그에 앞서 JSP/서블릿 개발에 필요한 다양한 개발 모델부터 살펴보자.



Model1

JSP로 웹 애플리케이션을 작성할 때 크게 두 가지 방식을 사용한다. Model1은 그 중 하나로 클라이언트의 요청을 JSP에서 받은 후 결과도 JSP에서 바로 처리하는 방식이다.

최초 JSP 개발에는 DB접속 처리 작업이나 비즈니스 로직이 그대로 남아 있는 등 JSP 파일 하나에 다양한 자바 로직이 들어가기도 했다. 이런 방식들은 효율적이지 못하다. 이후 커스텀 태그를 활용하거나 템플릿을 활용하는 등 개발의 효율화를 위해 다양한 컴포넌트들을 외부로 도출하기 위한 시도들이 이어졌다.

tech_120425_img29.jpg

Model1의 단점은 하나의 파일에 너무 많은 관심사가 들어 있는 것이다. 페이지에 대한 흐름제어, 비즈니스 로직 처리 및 다양한 스크립트 소스가 혼재돼 있다. 개선이 거듭된다 해도 이런 방식은 언젠가 한계에 부딪히게 된다.



Model2

두 번째 방식은 클라이언트 요청을 서블릿에서 담당하고 JSP로 결과 정보만 전달해 클라이언트로 JSP 페이지를 전달하는 Model2 방식이다.

Model2 방식으로 처리할 때는 JSP가 단순히 결과 출력에 관한 부분에만 관심을 갖게 된다. Model2는 JSP가 다양한 태그의 도움을 받고 자바 소스가 제거돼 Model1 방식에 비해 단순하다. 이 방식에서 주목할 점은 서블릿에서 모든 클라이언트의 요청을 처리한다는 것이다. 중앙에서 통제하는 역할자가 존재해 권한, 로깅, 보안 등 전체적인 시스템 통제를 수행할 수 있다. 이 방식에서는 비즈니스 로직이 서블릿과 JSP 페이지에 구분 없이 존재하는 경우를 주의해야 한다. 꼭 필요해서가 아닌 부주의로 인한 비즈니스 로직의 분산은 피해야 한다. 그렇지 않으면 후에 유지보수가 어려워질 수도 있다.



tech_120425_img30.jpg

MVC 패턴

Model, View, Controller를 의미하는 MVC는 애플리케이션 개발을 세 부분의 레이어로 구분해 처리한다. 모듈을 비즈니스 로직과 UI로 나누고 이를 제어할 제어부를 가진다.

- Model : 비즈니스 로직 및 데이터를 다룬다.
- View : UI 모듈로 화면 처리에 대한 부분을 담당한다.
- Controller : 요청을 분석해 로직을 처리하고 결과에 따른 View를 연결한다.

관심사의 분리(SoC, Separation of Concerns)는 중요한 설계 원칙 중 하나다. 이를 실현하는 방법 중 하나가 MVC 패턴으로 큰 프로그램을 데이터 처리, 출력 부분 등 의미 있는 집합의 모듈로 나눠 유지보수의 편의성을 취할 수 있다. 나눠진 모듈은 가독성을 높이며, UI 디자인과 비즈니스 로직이 분리돼 개발자와 디자이너 간의 협업이 수월하다. 또한 출력부가 다각화되면 여러 UI 플랫폼에서 동일 비즈니스를 실행해야 할 경우에도 개발이 한결 용이해 진다.

MVC 패턴은 웹 애플리케이션에서 어떻게 적용될까. <그림 3>은 일반 웹 애플리케이션에서 MVC 패턴을 적용한 모습이다.

tech_120425_img31.jpg

Model2의 서블릿과 MVC의 Controller, Model2의 JSP와 MVC의 View는 연관성이 있다. 또한 일반적으로 MVC 구현에는 Model2를 이용한다. 그래서인지 <그림 3>의 MVC 패턴은 Model2 방식과 비슷해 보인다. 하지만 Model2에는 MVC에 해당하는 Model이 정의돼 있지 않다. 그래서 일반적으로 자바빈을 이용하거나 일반 자바 클래스를 이 Model에 매칭시킬 수 있다.



MVC의 핵심, Controller

이제 지난 시간 예제로 작성했던 방명록을 MVC 패턴을 활용한 새로운 구조로 구현해 보자. Controller는 클라이언트의 요청을 분석한 후 적절한 비즈니스를 수행시켜 그 결과를 View로 전달한다. 기본적으로 Controller는 서블릿으로 작성하는데 HttpServlet을 상속받아 내용을 구현한다. HTTP의 대표적인 요청 방식인 POST, GET 방식에 대응하기 위해 doGet(), doPost()를 오버라이드(override)해 doProcess() 메소드를 호출하자. 대부분의 Controller 작업은 doProcess() 메소드에서 수행되기 때문이다.



<리스트 1> Controller 구현
/**
* GET 방식의 요청에 대응한다. doProcess()를 호출한다.
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doProcess(req, resp);
}/**
* POST 방식의 요청에 대응한다. doProcess()를 호출한다.
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doProcess(req, resp);
}/**
* 실제 클라이언트의 요청을 처리한다. Controller 역할을 하는 핵심 부분이다.
*/
protected void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String command = req.getRequestURI().substring(req.getContextPath().length());
command = GBUtil.removeLastString(command, ".do");

if(command == "select") {
SelectBusiness selectBiz = new SelectBusiness();
List list = selectBiz.selectList();
req.setAttribute("guestbookList", list);
RequestDispatcher dispatcher = req.getRequestDispatcher("/select.jsp");
dispatcher.forward(req, resp);

} else if( command == "delete") {
String id = req.getParameter("id");

DeleteBusiness deleteBiz = new DeleteBusiness();
int result = deleteBiz.delete(id);

req.setAttribute("result", result);
RequestDispatcher dispatcher = req.getRequestDispatcher("/delete.jsp");
dispatcher.forward(req, resp);

} else if( command == "update") {
String id = req.getParameter("id");
String name = req.getParameter("name");
String contents = req.getParameter("contents");

UpdateBusiness updateBiz = new UpdateBusiness();
int result = updateBiz.update(name, contents, id);

req.setAttribute("result", result);
RequestDispatcher dispatcher = req.getRequestDispatcher("/update.jsp");
dispatcher.forward(req, resp);

} else if( command == "insert") {
String name = req.getParameter("name");
String contents = req.getParameter("contents");

InsertBusiness insertBiz = new InsertBusiness();
int result = insertBiz.insert(name, contents);

req.setAttribute("result", result);
RequestDispatcher dispatcher = req.getRequestDispatcher("/insert.jsp");
dispatcher.forward(req, resp);

} else if( command == "update_form") {
String id = req.getParameter("id");
String name = req.getParameter("name");
String contents = req.getParameter("contents");

req.setAttribute("id", id);
req.setAttribute("name", name);
req.setAttribute("contents", contents);
RequestDispatcher dispatcher = req.getRequestDispatcher("/update_form.jsp");
dispatcher.forward(req, resp);

} else if( command == "insert_form") {
resp.sendRedirect("/insert_form.jsp");
}
}


Controller는 우선적으로 클라이언트의 요청을 분석하게 되는데 그 전에 클라이언트의 요청 정보를 어떻게 보낼 것인가에 대한 기준을 확인해야 한다. 즉 클라이언트가 조회를 원하는지, 삭제를 원하는지 혹은 어떤 서비스를 원하는지 서버에게 알려주기 위해 전달 방식의 기준을 확인하는 작업이 필요하다. 클라이언트는 크게 두 가지 방식으로 서버에 요청 정보를 전달한다.

첫 번째는 요청하려는 서비스를 URI 주소에 명시해 전달하는 방식(http://javacafe.or.kr/guestbook/update.doid=01)이다.

두 번째는 서버에 request의 매개변수로 요청 서비스를 전달하는 방식(http://javacafe.or.kr/guestbookcommand =updateid=01)이다.

이번 시간에는 첫 번째 방식을 사용한다. 클라이언트로부터 넘어오는 URI에서 원하는 서비스 문자열을 획득한 것을 확인할 수 있다. 컨텍스트 이하의 내용만 남겨두고 클라이언트가 요청하는 서비스를 확인하고 문자열 뒤의 “.do”를 제거해 깔끔한 서비스명을 받아온다.

서비스명을 확인한 후 그에 맞는 비즈니스를 수행하기 위해 if 구문을 사용했는데 이로써 Controller는 클라이언트의 요청을 제어하고 각 요청에 따른 비즈니스를 수행할 수 있게 된다. 각 if 구문에서는 크게 네 가지 작업을 수행하며 Controller는 서비스별로 작업을 수행하고 작업을 완료한다.

1) request로부터 매개변수를 조회한다.
2) 비즈니스를 수행하기 위해 메소드를 호출한다.
3) 결과를 request의 attribute에 담는다.
4) 결과 페이지를 설정한다.



web.xml 설정 정보 변경

Controller를 작성했다면 클라이언트의 요청을 하나의 Controller에서 제어할 수 있도록 web.xml을 수정해야 한다.


<리스트 2> web.xml 수정
MvcController
com.javacafe.example.common.MvcController
MvcController
*.do


MvcController라는 이름으로 Controller를 만들어 이를 web.xml에 등록해 보자. 서블릿 등록을 위해 태그를 사용하고 클라이언트 요청에 맞게 수행되도록 태그를 사용한다. <리스트 2>와 같이 .do로 URL 요청이 들어올 경우에는 앞서 만들어 둔 Controller로 처리한다.



비즈니스 로직과 데이터를 담당하는 Model

이제 실제 비즈니스 로직에 대해 살펴보자. 우선 서비스될 데이터를 저장할 수 있는 공간이 필요하다. 이를 위해서 자바빈을 사용한다. 또한 비즈니스를 수행하기 위해 일반 클래스를 사용한다. Controller, View와는 달리 비즈니스는 일반적인 자바 클래스다. 그래서 여기에 사용되는 비즈니스 로직과 데이터 클래스는 다른 환경에서도 재사용할 수 있다. 예를 들어 Swing을 이용해 만들어진 일반 프로그램 개발에서 사용하던 소스도 UI와 비즈니스가 분리된 환경이라면 얼마든지 재사용할 수 있다.



<리스트 3> 데이터를 저장할 GuestBookBean 클래스
public class GuestBookBean {
private String id
private int gbId
private String gbName
private String gbContents
/* 이하 getter/setter 생략 */
}


<리스트 3>은 애플리케이션에서 사용할 정보를 저장하게 될 클래스다. 필요한 항목을 기입하고 자바빈 규약에 맞게 getter/ setter 메소드만 포함해 클래스를 작성할 수 있다. 또한 이 객체를 활용하면 비즈니스 로직부터 JSP 화면까지 객체 단위로 개발할 수 있다.



<리스트 4> 비즈니스 로직을 담당하는 부분
public List selectList() {
List GuestBookList = new ArrayList();
try {
dbDisconnect();

pstmt = conn.prepareStatement("select * from gb_table order by gb_id");
rs = pstmt.executeQuery();

GuestBookBean gb = new GuestBookBean();
gb.setGbId( rs.getInt("gb_id") );
gb.setGbName( rs.getString("gb_name") );
gb.setGbContents( rs.getString("gb_contents") );
GuestBookList.add(gb);
dbDisconnect();
} catch (SQLException e) {
e.printStackTrace();
}
return GuestBookList;
}


<리스트 4>는 실제 비즈니스 로직을 담당하는 부분을 보여준다. 단순히 DB에서 방명록 목록을 조회하는 로직으로 이 소스만으로는 이 애플리케이션이 웹 애플리케이션인지 일반 애플리케이션인지 알 수 없다. 비즈니스 로직을 구현할 때는 가급적 request 객체를 직접 다루는 등의 작업을 삼가는 게 좋다. 그런 방식으로 비즈니스를 깔끔하게 구현하면 훨씬 관리하기 좋은 코드가 된다.



프레젠테이션을 담당하는 View

웹 애플리케이션에서는 View를 JSP로 구현할 수 있다. JSP 상에서 자바 소스가 많이 줄었을 뿐 이전 시간에 만들었던 방명록 예제와 흡사하다.


<리스트 5> View 만들기
<%
List list = (List) request.getAttribute("guestbookList");
if( list != null )
for(int i = 0; i < list.size(); i++) {
GuestBookBean gb = list.get(i);
%><%=gb.getGbId()%>
<%=gb.getGbName()%>
<%=gb.getGbContents()%>
삭제
수정 <%} %>


View에 해당하는 JSP 구성은 출력에 필요한 부분을 제외하고는 자바 로직이 존재하지 않는다. Model1 방식으로 작성된 최초의 소스에서 대부분의 자바 소스(Scriptlet)가 제거되고 HTML과 같은 태그 요소만 남게 된다. 분기/반복 처리를 위해 자바 소스가 일부 남아 있는 경우가 있지만 이마저도 EL(Expression Language)이나 커스텀 태그 등으로 대체할 수 있다. 지금까지 기본적인 MVC 패턴으로 웹 애플리케이션을 다시 작성해봤다. 그저 하나의 파일로 간단히 만들 수 있었던 프로그램을 여러 파일에 나눠서 개발해야 하니 복잡해 보일 수도 있다. 하지만 앞에서 언급한 것처럼 MVC 패턴 구현에는 많은 장점이 있기 때문에 직접 구현해 볼 가치가 있다.



다시 만드는 Controller

이제 작성한 애플리케이션을 좀더 확장성 있게 개선해 보자. 앞서 구축한 방명록 프로그램에 새로운 서비스가 추가되면 어떻게 해야 할까 예를 들어 selectOnlyOne 서비스가 추가된다고 가정한다면 이미 만들어진 MvcController 소스를 열어 if-else 구문의 가장 아래 부분에 항목을 추가해야 한다. 그리고 다른 로직과 유사하게 비즈니스 객체를 생성하고 메소드를 실행한 후 결과를 request에 담아 전달하는 작업도 수행해야 한다. 이 과정에서 어느 정도 공통되는 요소들이 존재함을 파악할 수 있다. 그래서 이 부분을 수정해 좀더 확장성 있는 MVC 패턴 Controller를 만들어 보려 한다.



tech_120425_img31.jpg

기존의 MvcContoller를 MvcService라는 별도의 서비스 클래스와 MvcView라는 클래스를 이용해 개선된 애플리케이션으로 만들 수 있다. MvcService는 각 서비스(명령)로부터 처리해야 할 업무를 별도의 클래스로 처리할 수 있어 확장이 가능하지만 기존 소스는 수정하지 않는다. 이런 디자인 원칙을 적용하면 훨씬 확장성 있고 유연한 프로그램을 작성할 수 있다. MvcView는 프레젠테이션 처리와 관련한 공통 요소를 저장하는 클래스로 비즈니스 로직 처리 후 수행되는 작업에 대한 주요 요소를 저장해 MvcController에서 쉽게 제어하도록 한다.



<리스트 6> MvcService와 MvcView 클래스 활용

/**
* 실제 클라이언트의 요청을 처리하는 Controller 역할을 하는 핵심 부분이다.
*/
protected void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String command = getCommandBy(req);
MvcService service = getServiceBy(command);
if( service == null ) {
throw new ServieNotExistException();
}
MvcView view = service.doServiceWith(req);
doHandleView(view, req, resp);
}


우선 MvcController의 핵심 메소드인 doProcess()부터 수정해보자. doProcess() 메소드에서 기존의 요청 서비스명(command) 조회 부분을 그대로 두고 서비스별 처리 부분을 수정한다. 기존에 각 서비스별로 존재하던 많은 if-else 구문이 사라진 것을 확인할 수 있다. getServiceBy() 메소드로 Mvc Service 객체를 조회하고 이 메소드의 doServiceWith()를 실행하면 각 서비스별로 존재하는 많은 소스들이 사라진다. 여기서 주목해야 할 것은 서비스 처리의 결과로 MvcView 객체가 생성된다는 점이다. 이를 처리하기 위해 doHandleView() 메소드를 호출한다.

<리스트 6>에서는 if-else 문이 존재하지 않아 서비스별로 어떤 업무를 처리하는지 알 수 없다. 세부적인 내용은 MvcService를 상속받은 서비스 클래스에서 이뤄진다. 이제 Controller는 서비스별 세부사항을 신경 쓰지 않게 됐다. 또한 MvcView라는 일정한 형태로 들어오는 결과를 통해 미리 정의한 내용만 신경 쓰면 되는 구조로 변했다.



<리스트 7> getCommandBy() 메소드

/**
* request 객체에서 요청 명령을 조회한다.
*/
private String getCommandBy(HttpServletRequest req) {
String command = req.getRequestURI().substring(req.getContextPath().length());
return GBUtil.removeLastString(command, ".do");
}


getCommandBy() 메소드는 request 내용으로부터 일정한 규칙에 따라 서비스명(command)을 추출하는 작업을 수행한다. MvcController 초기 버전의 앞 부분을 단순히 메소드로 도출했을 뿐이지만 메인이 되는 doProcess() 메소드가 깔끔하고 읽기 편해진다. getServiceBy() 메소드는 이번 MvcController의 핵심이다. 주어진 커맨드 문자열을 이용해MvcService 객체를 반환하는 작업을 수행한다.



<리스트 8> getServiceBy() 메소드
/**
* command 문자열을 기준으로 사전에 매핑된 MvcService 객체를 반환한다.
*/
private MvcService getServiceBy(String command) {
if( !serviceMap.containsKey(command) ) {
String domaindAndService = command.replaceAll("/", ".");
int dotIndex = domaindAndService.lastIndexOf(".");
domaindAndService = GBUtil.toUpperOnlyOneCharacter(domaindAndService, dotIndex+1);
String servicePath = "com.javacafe.example" + domaindAndService + "Service";

Object service;
try {
service = Class.forName(servicePath).newInstance();
} catch (Exception e) {
throw new ServieNotExistException(e);
}

if( service instanceof MvcService) {
serviceMap.put(command, (MvcService) service);
} else {
throw new ServieNotExistException();
}
}
return serviceMap.get(command);
}


커맨드와 수행될 서비스 간의 매핑을 어떻게 하는가에 대해서는 다양한 방법이 존재한다. 별도의 프로퍼티 파일이나 xml을 이용해 커맨드 문자열에 해당하는 서비스 객체를 매핑할 수도 있고 서비스 클래스에 annotation을 이용해 매핑할 수도 있다. 그 외에 개발 표준을 정해 커맨드에 일정한 규칙으로 해당 서비스를 찾게 할 수도 있다. 스프링 같은 프레임워크를 이용할 때는 이와 같은 다양한 매핑 방식 중 원하는 것을 선택할 수 있다. 여기서는 가장 간단한 방법인 세 번째 방법을 이용했다.

다음과 같이 커맨드 문자열에 해당하는 클래스 PATH를 매핑해 클래스를 찾는다.

[URL] http://javacafe.or.kr/mvc/guestbook/update.doid=01

[Command] guestbook/update

[Service] com.javacafe.example.guestbook.UpdateService

간단하게 커맨드와 서비스 클래스를 매핑해 봤다. get ServiceBy() 메소드는 두 가지 작업을 수행한다. 첫 번째는 커맨드를 분석해 해당 클래스를 유도하는 작업이고, 두 번째는 클래스의 객체를 만들어 반환하는 작업이다. Class.forName(), Class.newInstance() 등 클래스 객체의 메소드를 이용하면 클래스명만으로 런타임 시 객체를 생성하고 실행할 수 있다. 또한 많은 리소스가 소모되는 객체 생성은 맵에 넣어 두고 호출할 경우 다시 빼서 쓰는 형태로 구현할 수 있다. 이렇게 함으로써 두 번째 객체 반환은 조금 더 빠르게 반환된다. 이때 생성된 서비스 클래스는 기본적으로 MvcService를 구현한 클래스다.



<리스트 9> doServiceWith() 메소드

public interface MvcService {
public MvcView doServiceWith(HttpServletRequest req);
}



MvcService 인터페이스는 doServiceWith() 메소드만 존재하고 모든 서비스 클래스는 이 메소드를 구현한다. 최초 MvcController의 if 구문 안에 있던 내용이 이곳에서 처리된다. SelectService는 조회용 서비스로 MvcService를 구현하면서 doServiceWith() 메소드를 오버라이드한다.



<리스트 10> SelectService에서 doServiceWith() 메소드

public class SelectService implements MvcService {
SelectBusiness selectBiz = new SelectBusiness(); @Override
public MvcView doServiceWith(HttpServletRequest req) {
List list = selectGuestbookListFromDB();

MvcView view = new MvcView();
Map gbMap = new HashMap();
gbMap.put("guestbookList", list);
view.setModelMap(gbMap);
view.setViewPage("/select.jsp");

return view;
} private List selectGuestbookListFromDB() {
List list = selectBiz.selectList();
return list;
}
}


doServiceWith() 메소드는 비즈니스 로직을 호출해 클라이언트에서 요구하는 서비스를 수행한다. 그리고 그 결과를 반환하는데 이때 주목해야 할 부분은 결과물이 List 같은 데이터가 아닌 MvcView 객체라는 사실이다. 이 객체에 비즈니스 로직을 처리한 결과를 저장하고 포워딩될 JSP도 정의한다. 또한 redirect 처리 여부도 저장할 수 있도록 한다.



<리스트 11> MvcView 클래스

public class MvcView {
private boolean isRedirect;
private Map modelMap = new HashMap();
private String viewPage;
}



실제 MvcView 클래스의 정의를 보면 <리스트 11>과 같다. 이 정도만으로도 View 처리에 대한 충분한 정보를 담을 수 있다.



<리스트 12> MvcView 클래스 후처리

/**
* View 결과에 대한 마무리 작업을 처리한다. 주로 View에 있는 정보를 request, response에 세팅하는 작업이다.
*/
private void doHandleView(MvcView view, HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
if(view == null) return;

if( view.getModelMap() != null && !view.getModelMap().isEmpty() ) {
setRequestAttributes(req, view.getModelMap());
}

if( view.isRedirect() ) {
resp.sendRedirect(view.getViewPage());
} else {
RequestDispatcher dispatcher = req.getRequestDispatcher(view.getViewPage());
dispatcher.forward(req, resp);
}
}


모든 서비스 처리가 종료된 후 결과로 반환한 MvcView 클래스를 분석해 후처리를 수행한다. 기본적으로 비즈니스에서 처리됐던 결과 값을 request에 세팅하고, redirect 처리 여부를 확인해 적절한 응답을 클라이언트에 보내게 된다. 맵에 저장된 비즈니스 로직의 결과를 <리스트 13>과 같이 setRequestAttributes() 메소드를 통해 처리할 수 있다.



<리스트 13> setRequestAttributes() 메소드

private void setRequestAttributes(HttpServletRequest req, Map map) {
Iterator keyIterator = map.keySet().iterator();
while( keyIterator.hasNext() ) {
String key = keyIterator.next();
Object value = map.get(key);
req.setAttribute(key, value);
}
}



이렇게 Controller를 새롭게 구현해 보다 확장성 있고 유연한 MVC 패턴을 구현해 봤다. 단순한 방명록 프로그램이라 Controller도 단순하고 비즈니스도 단순했지만 더 복잡한 프로그램인 경우에는 더 많은 고려사항과 성능 이슈에 민감하게 대처해야 한다. MVC 패턴이 단순한 프로그램에는 어울리지 않는 옷일지도 모르지만 이번 시간을 통해 Model2 방식을 이용한 MVC 패턴 구현으로 보다 완성도 있는 애플리케이션을 구현할 수 있길 바란다.



프레임워크

MVC 패턴 구현은 여러 장점을 갖는다. 하지만 이를 직접 구현하는 일은 앞서 살펴봤던 것처럼 고려해야 할 부분도 많고 쉽지 않은 작업임은 틀림없다. 그러나 한번 잘 만들어 두면 유용하게 여러 프로젝트에서 사용할 수 있다. 그렇다 보니 다양한 오픈소스 형태로 MVC 프레임워크가 제공되고 있다. 대표적인 MVC 프레임워크로 스트럿츠가 있다.

그 외에 스프링에서도 MVC 패턴을 적용할 수 있도록 스프링 MVC가 제공되고 있다. 요즘 수행되는 대부분의 프로젝트는 이와 같은 프레임워크 기반에서 개발이 이뤄지고 있다. 다양한 프레임워크가 존재하고, 그런 프레임워크가 확장 가능한 형태로 제공되면서 MVC 프레임워크 외에 추가적인 프레임워크를 옵션으로 선택하는 것도 가능하다. 학습에 들인 노력만큼 크나큰 열매가 있기 때문에 개발자들에게는 버겁지만 반드시 학습해야 할 대상으로 생각된다.

이 시간에는 MVC와 자바에 대한 이해를 더하기 위해 직접 MVC 패턴을 구현해 봤지만, 사실 많은 프로젝트에서는 스프링 MVC와 같은 프레임워크를 활용하고 있다. 다음 시간에 소개할 스프링 프레임워크를 통해 MVC 패턴 이외에 다양한 웹 기술을 쉽게 활용하는 방법에 대해 알아보도록 하자.