2021년 목표설정

이미지
기본적으로 작년에 달성하지 못한 것들을 하려고 생각중인데..코로나가 언제까지 이어질지, 한국이나 북해도는 갈 수 있을지..자격증은 응시 가능할지..여러가지가 불확실하다. 2021년은 무엇보다 정신적인 부분과 경제적인 부분에 중점을 두고 조금 더 치열하게 지내보고 싶다. 일본나이로도 30대 마지막 해, 이제 불혹에 접어드는 나이..복잡하지만 심플하게. 육체적목표 : 트라이에슬론 스탠다드 도전하기 정신적 : 자격증2개 도전 + 자체개발 서비스 론칭 가족적 : 가정의 평화를 유지하기 경제적 : 외식과 유흥비를 줄이고 부수입을 늘려서 결과적으로 저축하기 사회적 : 목표세미나를 포함해서 민단과 개인인맥의 활성화와 교류를 촉진하기

JSP Model2(MVC 패턴)를 이용한 웹사이트 구축

JSP Model2(MVC 패턴)를 이용한 웹사이트 구축
작성자 : 김형준
작성일 : 2003.05.18
 전 : 1.0
본 글의 배포는 자유롭지만 작성자의 수고를 생각해서 출처 및 저자는 표기는
반드시 해 주시기 바랍니다.(www.jaso.co.kr)
무단으로 상업적인 용도로 사용할 수 없습니다.
Copyrightⓒ 김형준(babokim@unitel.co.kr) All right Reserved

1. 아키텍처
IT관련 기술의 변화속도는 새삼 설명할 필요가 없을 정도로 빠르게 변화하고 있다. 이러한 변화는 자바를 이용한 웹 시스템 구축과 관련된 기술분야에서도 예외가 아니다. 이런 빠른 변화속에서 현재의 주요 흐름 및 이슈를 파악하기 위해 필자는 서점을 자주 애용한다. 서점의 서가 전면을 차지하고 있는 주제들이 현재 이슈화되고 있는 기술이거나 향후 중요한 위치를 차지할 가능성이 있는 기술들이기 때문이다.
이러한 맥락에서 자바관련 서가를 보면 기존의 자바 기본 서적이나 JSP, Servlet 등의 단편적인 기술 위주의 책들 보다는 아키텍처를 주제로 다루거나 디자인 패턴, 프레임워크 등과 같은 J2EE를 이용하여 엔터프라이즈 시스템을 구축하는데 있어 어떻게 하면 효과적인 시스템(개발생산성, 재사용성, 유지보수성, 이식성, 확장성 등과 같이 프로젝트 일상에서 들어오던 좋은 말들)을 구축하는가에 초점이 맞추어진 내용이나 이와 관련된 기술로 구성된 책들이 서가의 전면을 구성하고 있다.
(필자의 경우 다른 분야(.NET)을 이용하여 프로젝트를 진행해본 경험이 없기 때문에 특별한 언급이 없는 한 앞으로 전개할 모든 내용들은 기본적으로 J2EE와 관련된 사항들이다.)
또한 이러한 책들에서 공통적으로 서론부분을 장식하고 있는 용어는 아키텍처이다. 그러면 이전에도 프로젝트를 진행할 때 아키텍처에 대한 사항들이 많이 나타나고 보편적으로 사용하고 있는데 왜 지금에서야 아키텍처라는 용어가 난무하기 시작하는지에 대한 의구심을 가져볼만하다.
이전의 프로젝트들에서 진행되어온 아키텍처는 주로 H/W, N/W의 구성과 같은 시스템의 물리적인 요소를 표현하는데 주로 사용되었으며, 개발할 소프트웨어에 대한 아키텍처는 미미한 수준에서 언급하고 지나치는 수준이었다. 따라서 개발자들은 각자의 스타일로 개발을 진행하게 되고, 프로젝트 완료 후 유지보수 단계에서 이를 인수 받은 유지보수 담당자는 복잡하게 얽혀 있는 다른 개발자들이 만든 소스로 매일 야근을 하는 상황이 발생하게 된다.
물론 프로젝트 분석/설계 단계에서 이러한 사항들을 제대로 정의하여 특정 프로그램 패턴으로만 개발을 진행할 경우 이러한 문제점은 발생하지 않을지도 모른다.
아키텍처에 대한 얘기는 추후에 이 주제에 대해 좀더 자세히 다루기로 하고 여기서는 이정도로만 언급하기로 한다.
그러면 이렇게 중요한 시스템의 전체 아키텍처를 어떻게 정의하면 되는가? 정답은 간단하다. 기존에 경험많고 똑똑하고 나서기 좋아하는 사람들이 미리 만들어 놓은 것을 가져다가 우리 프로젝트에 맞게 조금만 수정하면 된다.
이렇게 이미 검증되고 보편적인 아키텍처를 사용함으로써 시행착오를 줄일 수 있고 이미 이러한 아키텍처에 익숙한 개발자라면 별도의 아키텍처에 대한 교육이 필요 없으며, 또한 공통된 아키텍처가 개발자들 사이에 공유됨으로써 자연스럽게 개발자들 사이에 커뮤니케이션이 가능하게 된다.
또한, 유지보수로의 이전시에도 실제 프로젝트에 참가하지 않는 또 다른 개발자라 할지라도 해당 비즈니스만 이해하고 있으면 큰 어려움 없이 유지보수가 가능하게 된다.
웹 시스템과 관련된 아키텍처 중에서 가장 많이 사용되고 있으며 현재의 주류로 자리잡고 아키텍처가 바로 MVC 모델에 기반을 둔 JSP Model2 구조이다. (JSP Model2에 아키텍처라는 거창한 용어를 붙이는 것이 적당할지는 모르겠지만 그래도 전체 어플리케이션의 구조를 결정하는 역할을 담당하기 때문에 아키텍처라고 하였다.)
2. JSP Model 1
지금까지의 JSP를 이용한 웹 시스템의 대부분은 JSP 페이지와 공통 유틸리티 성격의 클래스를 활용하는 방식으로 구성되어 있었다. 그러면서 화면 JSP, 업무처리용 JSP 사이를 이동 하면서 사용자의 요구사항을 구현하거나 심지어는 하나의 JSP에서 모든 것을 처리하게 하는 경우도 있다. 이러한 구성을 JSP Model1이라고 부른다.
다음 그림은 JSP Model1 구조를 나타낸 그림이다.

<v:f eqn="if lineDrawn pixelLineWidth 0 "></v:f><v:f eqn="sum @0 1 0 "></v:f><v:f eqn="sum 0 0 @1 "></v:f><v:f eqn="prod @2 1 2 "></v:f><v:f eqn="prod @3 21600 pixelWidth "></v:f><v:f eqn="prod @3 21600 pixelHeight "></v:f><v:f eqn="sum @0 0 1 "></v:f><v:f eqn="prod @6 1 2 "></v:f><v:f eqn="prod @7 21600 pixelWidth "></v:f><v:f eqn="sum @8 21600 0 "></v:f><v:f eqn="prod @7 21600 pixelHeight "></v:f><v:f eqn="sum @10 21600 0 "></v:f><o:lock v:ext="edit" aspectratio="t"></o:lock>
[그림] JSP Model1 구조
그림에서 보는 것처럼 JSP에서 직접 DB 처리를 하거나, 일부 유틸리티 성격의 Bean 클래스를 이용하여 DB 처리를 하고 있다, 사용자의 요청과 비즈니스의 처리, 처리된 결과 화면으로의 이동은 여러 단계의 JSP를 거치면서 처리되고 있다.
[JSP Model1 구조의 소스 예제]
<%
String selectStmt = "SELECT * FROM T_BOARD" ;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
conn = DBUtil.getConnection();
pstmt = conn.prepareStatement(selectStmt);
rs = pstmt.executeQuery();
%>
<HTML>
<HEAD>
<TITLE>게시판 목록 조회</TITLE>
</HEAD>
<BODY>
<TABLE>
<%
while(rs.next()) {
%>
<TR>
<TD><%= rs.getString(2) %></TD>
<TD><%= rs.getString(5) %></TD>
</TR>
<%
}
%>
</TABLE>
</BODY>
</HTML>
<%
}
catch(Exception e) {
throw e;
}
finally {
DBUtil.closeConnection(conn, pstmt, rs);
}
%>
이러한 구성의 장점은 소규모의 시스템에서 빠르게 개발이 가능하고, 클래스에 대한 별도의 컴파일 작업이 필요 없으며, 스크립트언어 위주이기 때문에 객체지향에 대한 개념이 없는 개발자들만으로도 개발이 가능하다.
(이렇게 컴파일이 필요없는 JSP의 특징이 자바를 처음 시작하는 개발자들에게는 마약과 같다라고 필자는 생삭한다.
자바를 처음 시작하는 개발자들은 classpath, package, javac와 같은 환경적인 요소에서 어려움을 많이 겪고 있다. 이런 상황에서 JSP를 접하게 되고 JSP의 컴파일이 필요없는 특징 때문에 JSP 위주로 개발을 진행하게 된다. 이렇게 되면 자바를 이용하여 웹시스템을 몇번 구축해본 개발자라 할지라도 제대로 컴파일조차 하지 못하는 경우가 발생하게 된다.)
단점은 JSP내에 비즈니스 로직이 구현되어 있기 때문에 유사한 비즈니스 로직이 여러 페이지에 걸쳐 존재하게 된다. 이렇게 코드의 중복이 발생하게 되면 로직 변경시 수정해야 할 부분이 많다는 것을 의미한다. 이부분에 대해서도 공통적으로 사용하는 기능에 대해서는 클래스로 도출하여 사용하면 되지 않냐고 반론을 제기할 수도 있다. 하지만 JSP를 선호하는 개발자들이 자발적으로 클래스로 도출을 하느냐는 것이다.
프로젝트 초기에 구성된 문자열처리, 시간처리, DB관련 공통 클래스를 제외하고 개발자들에게 공통 기능을 도출하면서 개발하게 하는 것은 애초부터 무리이다. 의식이 깨어있는 개발자라면 몰라도 말이다.
또 다른 단점으로는 개발자들에 따라 다양한 형태의 코드가 만들어진다는 것이다. 앞에서 아키텍처 설명시 언급한 사항인데 이부분도 개발 가이드를 적절하게 제시함으로써 어느정도 표준화된 소스코드를 만들 수 있지만 이것만으로는 부족하다.
3. JSP Model 2
그러면 앞에서 설명한 JSP Model 1의 단점을 해결할 수 있는 방안은 무엇인가?
먼저 코드의 중복 부분을 해결해 보자.
모든 비즈니스 로직은 자신이 속한 업무 영역(도메인)의 기능을 처리하기 위해서나, 자신이 속하지 않은 다른 업무 영역에서 호출되어지건 간에 언젠가는 재사용이 가능하다는 가정에서 출발하면 된다.
이러한 가정이 수립되면 모든 비즈니스 로직이 공통 기능이기 때문에 JSP(화면)로부터 이러한 비즈니스 로직을 분리하여 별도의 클래스로 구성을 해야 할 것이다.
두번째 문제인 검증된 구조가 아닌 개발자 고유의 구조로 개발이 진행되는 문제인데, 이 문제는 이미 검증된 구조를 사용함으로써 해결할 수 있다.
이렇게 화면 Layer와 비즈니스 Layer를 분리하면서 이미 많은 시스템에서 검증된 구조가 바로 JSP Model 2 이다.
JSP Model 2 구조를 그림으로 나타내면 다음과 같다.

[그림] JSP Model2 구조
JSP Model 2 구조는 앞에서 설명되었던 JSP Model 1 구조의 문제점을 해결하고자 J2SE의 Swing component에 적용되어진 MVC Model을 응용하여 웹 시스템으로 적용한 것이라 할수 있다.
따라서 JSP Model2 구조의 기본은 MVC Model 구조라 할 수 있다.
MVC라는 용어에 대해 처음 접해보는 독자도 있을것이라 예상되는데 간단하게 설명하면 다음과 같다.
MVC Model은 하나의 기능을 수행하기 위해 어플리케이션을 여러 개의 계층으로 나누는데 데이터 부분을 담당하는 Model, 화면을 담당하는 View, 사용자의 Event 처리 및 어플리케이션의 흐름을 제어하는 Controller 이렇게 세가지고 나누어 각자의 역할을 수행하는 구조이다. 이러한 구조의 장점은 각 Layer의 변경 사항은 다른 Layer에 영향을 최소화하여 어플리케이션 변경시에 유연하게 대처 가능하다는 것이다. MVC 구조는 Swing의 JTable을 이용하는 프로그램을 몇번 해보면 금방 알 수 있다.
이런 MVC 모델을 웹 시스템에 적용하면 그림에서 보는 것과 같이 Model은 데이터를 처리하는 일반 Bean 클래스가, View는 JSP가, Controller는 Servlet이 담당하게 된다.
l Model : Business Logic과 데이터베이스 입출력을 담당하는 Layer. View와는 독립적
l View : 어플리케이션의 유저 인터페이스만 담당
최소한의 코드를 이용하여 단순하게 작성
l Controller : 어플리케이션의 Flow를 담당
View와 Business Logic의 중재자 역할
사용자의 특정 서비스 요청을 받아서, 해당 로직에 연결
세션 관리, 권한관리 등의 기능 담당
Front Controller
현재에는 이런 MVC 구조에 여러가지 패턴들을 붙여 프로젝트마다 조금씩 다른 형태를 가지고 개발을 진행하고 있다.
앞에서 아키텍처에 대한 설명을 했는데 건축에서 보면 그리스 양식, 로마 양식이라고 특정 아키텍처를 지칭하게 되면 건축과 관련된 사람들의 머리속에는 공통적으로 동일한 그림이 그려질 것이다. 이러한 아키텍처를 기반으로 지어진 건물들은 공통된 특징을 가지고 있지만 나름대로 톡특한 특성을 가지고 있다. 이러한 아키텍터는 수세기에 걸쳐 많은 건축가들에 의해 진보하고 있었던 것이다.
이제 IT 분야에서도 이러한 시도가 진행되고 있는데 JSP Model2를 적용한 모든 프로젝트가 동일한 구조를 가지고 있지 않은 이유도 이러한 이유때문이다.
그러면 이론적인 설명은 여기서 마치고 이제부터 실제 JSP Model2를 이용하여 구축된 사이트의 소스를 보며 실제 개발에서는 어떻게 적용되는지 살펴보자.
한가지 당부의 말을 한다면 JSP 만을 이용하여 구축했던 개발자들에게 Model2에 대해 설명하고 그렇게 구축하라고 하면 대부분의 개발자들은 왜 그렇게 번거롭게 개발을 진행하는지에 대해서 반론을 제기한다. 또한 개발 생산성이 저하된다고 불평을 하기도 한다.
하지만 분명한 것은 JSP Model2 는 훨씬 진보된 개념이고 장점이 많으며, JSP 위주의 개발자들이 생각하는 것 보다 생산성이 저하되지 않는다는 것이다(ANT 등의 도구를 이용하면 컴파일을 크게 문제되지 않는다). 또한 향후 지속적인 유지보수나 다양한 클라이언트(브라우저, PDA, 핸드폰 등)가 요구되는 시스템에서는 필수라 할수 있다.
또한 현재의 주요 트렌드가 CBD(Componet Based Deveploment)인데 CBD는 하루아침에 할 수 있는 것이 아니다. 클래스와의 연관관계, 상속, 패턴들의 활용 등을 익힘으로써 CBD로의 전환을 쉽게 할 수 있다는 것이다(물론 JSP Model2 구조 만으로도 CBD의 적용이 가능하다).
지금 당장 귀챦다고 불평하지 말라! 그 불평속에는 변화를 무서워하거나 새로운 것을 공부하기 싫어 하는 본심이 숨어있지 않은지 생각해보기 바란다.
4. JSP Model 2 구현 사례
앞에서 설명 했듯이 기본적인 JSP Model2 구조는 MVC 구조인데 프로젝트의 환경에 맞게 변경이 가능하다고 하였다. 필자가 선택한 구조는 다음과 같은 구조를 가지고 있다.

[그림] public Project 어플리케이션 구조
시스템의 아키텍처를 설명하는데 있어 보통은 클래스 다이어그램을 활용하지만 UML에 익숙하지 않은 독자들을 위해 일반적인 그림으로 표현하였다. 그림에서 노란색으로 표시된 것들은 일반 클래스들(JSP, Servlet, EJB 등과 상관없는 일반적인 클래스)이다. 앞에서의 기본적인 JSP Model 2 구조와는 조금 더 복잡한 형태를 취하고 있다. 내부적인 MVC 구조 외에 크게 두가지 Layer로 분리하였다.
웹계층과 비즈니스계층인데 웹계층은 HTTP에 대한 처리를 담당하는 계층이다. 따라서 HTTP 요청에 종속적이며 HTTP에 대한 Request, Response, Session에 대한 처리를 담당하게 된다.
비니지스계층은 HTTP 요청과 무관하게 순수하게 비니지스 로직 처리만을 담당하는 계층이다. 따라서 관련 클래스들은 HttpServletRequest. HttpServletResponse 등 HTTP와 관련된 클래스와는 무관하게 동작한다. 이렇게 비즈니스 계층을 별도로 분리한 이유는 한번 개발된 비즈니스 로직은 Servlet Container 뿐만 아니라 배치프로그램, 타 시스템과의 인터페이스, 여러 종류의 이기종 클라이언트 등의 용도로 사용을 할 수 있기 때문이다.
위의 구조를 이용하여 목록 조회를 처리하는 경우 다음과 같은 순서로 처리된다.
1. 사용자는 브라우저에서 GET, POST 방식으로 Controller(Servlet)을 호출한다.
이때 HttpServletRequest에 목록 조회 명령 요청을 인식할 수 있는 파라미터를 같이 전송한다.
(board는 servlet mapping을 통해 board.BoardServlet으로 연결되어 있다)
2. 요청을 받은 Servlet은 command 파라미터를 추출하여 CommandFactory에게 적절한 Command 클래스를 요청한다.
3. CommandFactory는 해당 command를 처리하는 Command 객체를 생성하여 Servlet으로 반환한다.
4. Servlet은 Command 클래스의 execute() 메소드를 호출하여 목록 조회를 처리한다.
5. Servlet으로부터 처리 요청은 받은 Command 클래스는 HttpServletRequest로부터 목록 조회에 필요한 정보(페이지, 검색조건, 키워드 등)를 추출하여 Control 클래스의 적절한 비니지스 처리 메소드로 목록 조회 처리를 의뢰한다.
6. Control 클래스는 적절한 목록 조회를 위한 비즈니스 로직을 처리하거나 데이터 Access가 필요한 경우 Data Access Object(DAO)에 데이터 조회 처리를 의뢰한다. 목록 조회 결과는 Value Object 형태로 만들어서 다시 Command로 반환한다.
7. Command 클래스에서는 처리 결과로 받은 Value Obejct를 HttpServletRequest의 Attribute에 setting한 후 이동할 페이지의 URL(BoardListView.jsp)을 문자열로 반환한다.
8. Servlet은 Command 클래스에서 return된 이동할 페이지의 URL로 forward한다.
9. 목록 조회 JSP에서는 HttpServletRequest에 setting된 목록 데이터를 가져와 화면에 나타낸다.
이러한 흐름을 표현하기 위해 UML에서는 시퀀스 다이어그램을 사용한다. 다음 그림이 위의 흐름을 시퀀스다이어그램으로 나타낸 것이다.

[그림] 목록조회 시퀀스다이어그램
그림은 두개의 큰 부분으로 나누어져 있는데 앞단에 있는 부분이 Http의 request, response에 대한 처리를 담당하고 뒤에 있는 부분이 실제 비즈니스 로직과 Data에 대한 처리를 담당하게 된다.
필자가 정의한 아키텍처에서는 Entity 클래스에 대해서는 정의를 하지 않고 있다. JSP Model 2 구조에 대해서 생소한 독자들에게 Entity 개념까지 포함된 아키텍처를 구성하게 되면 너무 어려워 질 것 같아 Entity를 빼고 Control 까지만 구성하는 아키텍처를 만들었다.
실제 이러한 구조로 소규모의 프로젝트를 진행해본 결과 기존의 구조적 분석 방법으로 접근하여 개발해온 개발자들도 무난하게 진행을 개발을 진행할 수 있었다.
개발자들의 기술이 좀 더 upgrade된 상태에서 Entity까지 적용하고 더 나아가 CBD까지 확대해 보는 것이 좋을 듯하다.

[그림] public Project 아키텍처 클래스다이어그램


그러면 아키텍처를 구성하는 각 요소들의 역할과 실제 구현 예를 알아보자.
◆ Controller
- Servlet 클래스로 구현
- 사용자(브라우저)의 모든 요청의 Controller로 요청
- 사용자의 요청을 판단하여 해당 기능을 수행
- 기능 수행 후 사용자에게 결과 페이지 forward
- 일반적으로 서브 시스템 또는 전체 시스템에 1개
- web.xml 파일의 servlet 맵핑 이용
- CommandFactory를 이용
[소스] Controller
package home.board;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import home.common.*;
public class BoardServlet extends HttpServlet
{
protected ServletConfig config;
public void init(ServletConfig config) throws ServletException
{
super.init(config);
this.config = config;
}
public void destory()
{
config = null;
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException
{
String nextUrl = null;
String commandStr = request.getParameter("command");
try
{
Command command = BoardCommandFactory.getCommand(commandStr);
nextUrl = command.execute(request, response, config);
}
catch(Exception e) {
request.setAttribute("javax.servlet.jsp.jspException", e);
nextUrl = CommonProperty.ERROR_PAGE;
}
RequestDispatcher rd = getServletContext().getRequestDispatcher(nextUrl);
try
{
rd.forward(request, response);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
Controller는 해당 시스템의 모든 사용자 요청을 받으며 페이지의 이동을 책임진다.
따라서 시스템 내에서는 Controller를 거치지 않고는 화면을 나타내서는 않된다. 이런 방식에 대해서는 뒤의 Command 설명시에 자세하게 나올 것이다.
Controller는 Servlet으로 구현하지만 JSP로도 구현이 가능하다. JSP로 구현했을때에는 별도의 컴파일이 필요없다는 장점이 있지만 Controller는 한번 컴파일되면 거의 변경사항이 없고 사용되는 성격상 Servlet으로 작성한다.
Controller에서는 Command의 execute() 메소드를 수행하게 되는데 이때 Command 단 이후에 발생되는 Exception에 대해서 사용자에게 알려줄 필요가 있는 경우 해당 Exception을 Controller까지 throws 시킨 다음 위의 소스에서 처럼 Exception 발생시에 다음과 같이 처리한 후 Error Page로 forward 시킬 경우 Error 페이지의 exception 내장 객체를 사용하여 에러 메시지를 가져올 수 있다.
request.setAttribute("javax.servlet.jsp.jspException", e);
위와 같이 처리한 경우에는 Error JSP 페이지로 이동 시키는데 이때 Error JSP 페이지는 isErrorPage 속성이 true로 설정되어 있어야 한다.
◆ Command
- Command 자체는 interface 형태
- Action 이라는 용어로도 사용되어 진다.
- Command 자체는 전체 시스템에 1개만 존재
- Command Interface에는 사용자의 요청을 처리하는 기능을 수행하는 메소드 정의
execute(HttpServletRequest request, HttpServletResponse response);
- 실제 사용자의 요청을 처리하는 로직은 Command Interface를 implements한 클래스에 구현
- 하나의 Command 클래스에는 하나의 단위 기능만 처리
InsertCommand, UpdateCommand, DetailViewCommand, ListViewCommand 등
- 등록 처리 후 목록 조회로 이동하는 기능을 처리하는 경우 등록 처리 Command, 목록 조회 Command를 별도로 구성하여 등록처리 Command에서 해당 기능을 수행한 후 목록조회 Command의 execute() 메소드를 호출하여 목록 조회 처리 수행
- HttpServletRequest로부터의 파라미터 추출 및 Control의 비니지스 메소드 호출, 처리 결과를 다시 HttpServletRequest에 설정, 처리 후 이동할 URL의 반환 등의 기능을 수행
- 주의해야 할 사항은 실제 비니지스 로직에 대한 처리가 Command에 존재하면 안됨
(개발을 진행하다 보면 자신도 모르는 사이에 비즈니스 로직이 Command에서 처리되고 있는 경우가 있음 -> 이러한 상황에서는 리팩토링을 통해 Control 클래스로 해당 기능을 이동하는 것이 바람직하다.)
[소스]Command Interface
package home.common;
import javax.servlet.*;
import javax.servlet.http.*;
public interface Command
{
public String execute(HttpServletRequest request,
HttpServletResponse response,
ServletConfig config) throws Exception;
}
[소스] Command를 implements한 BoardListViewCommand
package home.board.command;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import home.common.*;
import home.board.*;
import home.board.vo.*;
public class BoardListViewCommand implements Command
{
String nextUrl = "/jsp/board/BoardListView.jsp";
public String execute( HttpServletRequest request ,
HttpServletResponse response,
ServletConfig config ) throws Exception
{
try
{
String boardType = request.getParameter("boardType");
if(boardType == null || boardType.length() == 0)
{
boardType = (String)request.getAttribute("boardType");
}
if(boardType == null || boardType.length() == 0)
{
boardType = "A";
}
SearchVO searchVO = new SearchVO();
searchVO.setSearchType(request.getParameter("searchType"));
searchVO.setKeyword(request.getParameter("keyword"));
String page = request.getParameter("page");
if(page == null || page.length() == 0)
{
searchVO.setCurrentPage(1);
}
else
{
searchVO.setCurrentPage(Integer.parseInt(page));
}
BoardCtrl boardCtrl = new BoardCtrl();
PageVO pageVO = boardCtrl.getBoardList(boardType, searchVO);
request.setAttribute("pageVO", pageVO);
request.setAttribute("searchVO", searchVO);
request.setAttribute("boardType", boardType);
}
catch(Exception e)
{
LogUtil.error(getClass().getName() + ":" + e.getMessage());
throw e;
}
return nextUrl;
}
}
필자의 경우 Command에 대한 클래스 명명 규칙으로 화면 호출에 대한 Command인 경우 ViewCommand라는 접미사를 붙이고 일반적인 로직 처리만 하는 경우 Command라고 붙인다. 예를 들어 게시판목록조회의 경우 BoardListViewCommand가 되고 게시판입력의 경우 BoardInsertCommand가 된다. 물론 등록 후 다시 목록으로 이동하지만 목록화면이 나타나는 것은 BoardInsertCommand가 처리하는 것이 아니라 BoardListViewCommand가 처리하고 BoardInsertCommand 는 순수하게 Insert 처리만 담당하기 때문에 명명 규칙을 위와 같이 정하였다.
실제 개발을 진행하다보면 Command 클래스와 JSP가 가장 많이 생성되는 파일이다. 그렇기 때문에 이러한 명명규칙을 정해 놓지 않으면 개발자들 사이에 임의로 작성하기 때문에 프로젝트 전체의 소스에 일관성이 없어지고 타인이 개발한 소스에 대한 가독성이 떨어지게 된다.
Controller와 Command까지 설명을 하였는데 모든 화면(JSP)은 반드시 Controller를 거쳐 Command를 통해 수행되게 하여야 한다. 따라서 시스템 상에서 다음과 같은 URL은 발생할 수 없다.
아무런 비즈니스적인 처리가 없이 단순히 Insert 화면을 호출하는 기능이라 할지라도 Command를 거쳐 화면이 호출되게 해야 한다. 번거롭지만 이렇게 구성하는 이유는 대부분의 프로젝트에서는 공통적으로 적용해야 할 사항들이 있다. 해당 화면에 대한 권한 인증, 화면 접근에 대한 로그 기록 등등이 이러한 내용들인데 이러한 내용들은 비즈니스 로직적인 내용들이다. 따라서 이러한 기능을 처리하기 위해서라도 반드시 Command를 거쳐야 한다.
또 다른 이유는 사용자의 요구에 의해 입력화면에 DB로부터 코드 정보를 가져와 <SELECT>로 나타내게 변경해야 하는 경우 DB로부터 코드정보를 가져와야 하는데 이 경우 직접 JSP가 호출된 경우에는 JSP에 이러한 로직을 추가할 가능성이 높기 때문이다. 이렇게 아키텍처에 위배되는 코드가 많이 발생하는 것은 바람직한 현상은 아니다. 이런 것들을 방지하기 위해서는 최초 개발시에 조금 불편하더라도 모든 화면을 Command를 거쳐 처리하도록 하는 것이 현명한 선택일 것이다. (index.jsp와 같은 특수한 상황은 제외한다.)
Command의 execute() 메소드는 처리를 완료한 후 return 값으로 이동할 JSP의 URL을 반환한다. 이때 간단하게 구현하는 방법은 위의 소스처럼 멤버변수로 가지고 있거나 return 문 자체에서 URL의 문자열을 반환하면 된다.
하지만 이러한 화면의 이동에 대한 사항을 각각의 Command에서 관리하게 되면 이동사항이 변동되었거나 전체 시스템의 URL 경로가 변경되는 경우에는 모든 Command를 변경해주어야 하는 번거로운 작업을 해야한다. Struts와 같은 프레임워크는 이러한 URL에 대한 정보를 XML 파일에 저장시킨 후 Command에서는 XML 파일에 맵핑되어 있는 이름을 이용하여 URL 정보를 가져온다. 실제 프로젝트에서도 이와 유사하게 구현하는 것이 더 좋은 구현이 될 것이다.
[struts의 URL 맵핑 정의(struts-config.xml)]
<action path="/editSubscription"
type="org.apache.struts.webapp.example.EditSubscriptionAction"
attribute="subscriptionForm"
scope="request"
validate="false">
<forward name="failure" path="/mainMenu.jsp"/>
<forward name="success" path="/subscription.jsp"/>
</action>
[맵핑정의 활용소스]
public class EditSubscriptionAction extends Action
{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
if()
{
return (mapping.findForward(“success"));
}
else
{
return (mapping.findForward(“failure "));
}
}
}
◆ CommandFactory
- 사용자의 request를 처리할 적절한 Command 객체를 생성하여 반환
[소스] CommandFactory
package home.board;
import java.io.*;
import java.util.*;
import home.common.*;
import home.board.command.*;
public class BoardCommandFactory
{
public static Command getCommand(String commandStr) throws Exception
{
if("BoardListView".equals(commandStr))
{
return new BoardListViewCommand();
}
else if("BoardDetailView".equals(commandStr))
{
return new BoardDetailViewCommand();
}
else if("BoardInsertView".equals(commandStr))
{
return new BoardInsertViewCommand();
}
else if("BoardInsert".equals(commandStr))
{
return new BoardInsertCommand();
}
else
{
throw new Exception("요청에 대한 Command 정의가 되어 있지 않습니다.");
}
}
}
 Control
- 실제 비즈니스 로직을 처리하는 클래스
- EJB로 구현할 경우 Session Bean으로 구현됨, Entity Bean을 생성하고 다른 Session Bean의 기능을 호출
- EJB가 아닌 경우 일반 비즈니스 기능 수행, DAO를 통해 데이터를 조회하거나 트렌젝션에 대한 처리 등과 같은 비니지스 로직 처리
- HttpRequest, HttpResponse, HttpSession 등 HTTP요청과는 무관하게 구현
- 하나의 업무(CRUD)별로 하나씩 존재
[소스] 비니지스 로직을 처리하는 Control 클래스
public class BoardCtrl
{
public BoardVO[] getBoardList(String keyword) throws Exception
{
Connection conn = null;
try
{
conn = DBUtil.getConnection();
BoardDAO boardDAO = new BoardDAO(conn);
return boardDAO.selectBoardList(keyword);
}
catch(Exception e)
{
throw e;
}
finally
{
DBUtil.closeConnection(conn);
}
}
public void insertBoard(BoardVO boardVO, AttachVO[] attachVOs) throws Exception
{
//Insert 처리 로직
}
public BoardVO getBoard(String boardId) throws Exception
{
//게시 상세정보 조회 처리 로직
}
}
외부시스템과 연계가 필요한 경우 Control 클래스는 외부시스템과의 interface 역할을 수행하게 된다. 물론 외부 시스템에서 해당 시스템의 Control 클래스를 원격 호출할 수 있는 적절한 메커니즘이 제공되어야 한다. 이러한 메커니즘으로는 RMI, EJB, Web Services 등이 있다.
최초에 시스템을 개발할때에는 외부와 인터페이스가 발생하지도 않고 외부에서도 개발되는 시스템의 기능 또는 데이터를 사용하지 않았는데, 시스템이 운영되면서 데이터가 축척되고 외부에 새로운 시스템들이 개발되면서 타 시스템에서 해당 시스템의 기능을 수행시키고자 한다면 어떻게 하겠는가?
앞에서 설명한 Control 클래스와 같은 비즈니스 로직을 처리하고 HTTP에 독립적인 클래스로 구축하지 않은 경우에는 JSP에 있거나 기타 다른 일반 클래스에 구현되어 있는 로직들을 모아 다시 재구성해야 할 것이다. 하지만 Control 클래스로 도출되어 구현되어 있는 경우 요즘 이슈가 되고 있는 Web Services로 등록하는 경우 별도의 수정없이 deploy description의 작성만으로 외부에 서비스가 가능하게 된다(이부분에 대해서는 향후 연재할 예정이다).
이러한 사항은 시스템 내부적으로도 마찬가지이다. 일반적으로 하나의 시스템은 여러 개의 서브 기능으로 나누어 진다. 지금까지는 이러한 서브시스템간의 인터페이스에 대해서는 생각을 하지 않고 하나의 시스템으로 간주하여 A라는 서브시스템에서 B라는 서브시스템의 테이블 정보를 조회하거나 수정, 심지어는 삭제하는 기능까지 처리하는 경우도 있었다.
이러한 구성의 단점은 A 서브시스템의 메인 개발 조직에서 어떠한 이유에 의해 테이블의 컬럼명을 변경하거나 컬럼을 추가하는 등의 테이블의 구조가 바뀌는 경우 B 서브시스템의개발 조직은 A 서브시스템의 개발조직에서 변경사항을 통보받거나, 시스템에서 에러가 발생한 다음에야 이를 인지할 수 있다. 또한 B 서브시스템은 반드시 A 서브시스템에 있는 테이블이 존재해야지만 시스템의 운영이 가능하기 때문에 B 서브시스템 단독으로 하나의 비지니스 Component로 활용을 할 수 없다는 것이다.
따라서 Control 클래스를 사용하는 장점을 더 살리기 위해서는 각 서브시스템들은 타 서브시스템에 있는 데이터 또는 비즈니스 로직에 대한 처리가 필요할 경우 해당 서브시스템의 Control에 있는 메소드를 호출해서 수행하도록 해야 한다. 물론 Control 클래스에 대한 interface를 먼저 정의한 다음 구현하는 것이 더욱 좋겠지만 여기서는 이렇게까지 구현은 하지 않았다.

[그림] Control 클래스를 이용한 내/외부 시스템 연계


 DAO(Data Access Object)
- 데이터베이스 관련 처리만 하는 클래스
- 비즈니스 로직이 들어가지 않도록 주의
- 이식성이 높은 시스템을 구축할 경우 DAOFactory를 사용
DBMS별로 DAO를 구성하여 Factory에서 적절한 DAO 반환
- 다른 서브 모듈에서 직접 DAO를 호출하지 않아야 함
해당 기능을 수행하는 Control을 이용하여 호출
- 테이블 또는 CRUD 기능별로 하나의 파일로 구현
- 성능을 고려하여 가능한 PreparedStatement를 사용
- 처리결과를 ResultSet 또는 Collection(Vector, ArrayList)등의 형태로 결과를 반환하지 말것. 소스의 가독성이 떨어짐
[소스] Data Source Object
public class BoardDAO
{
Connection conn = null;
public BoardVO[] selectBoardList(String keyword) throws Exception
{
String selectStmt = “SELECT * FROM T_BOARD WHERE TITLE LIKE ?”;
PreparedStatement pstmt = null;
ResultSet rs = null;
BoardVO[] boardVOs = null;
try
{
pstmt = conn.prepareStatement(selectStmt);
pstmt.setString(1, “%” + keyword + “%”);
rs = pstmt.executeQuery();
ArrayList arrayList = new ArrayList(10);
while(rs.next())
{
BoardVO boardVO = new BoardVO();
boardVO.setBoardType (rs.getString( 1));
boardVO.setBoardId (rs.getInt ( 2));
boardVO.setTitle (rs.getString( 3));
boardVO.setContents (rs.getString( 4));
boardVO.setRegDate (rs.getString( 5));
boardVO.setUserId (rs.getString( 6));
boardVO.setUserName (rs.getString( 7));
boardVO.setUserMail (rs.getString( 8));
boardVO.setReadCount (rs.getInt ( 9));
boardVO.setPassword (rs.getString(10));
boardVO.setStep (rs.getInt (11));
boardVO.setParentId (rs.getInt (12));
arrayList.add(boardVO);
}
if(arrayList.size() > 0)
{
boardVOs = new BoardVO[arrayList.size()];
arrayList.toArray(boardVOs);
}
}
catch(Exception e)
{
throw e;
}
finally
{
DBUtil.closeConnection(pstmt, rs);
}
return boardVOs;
}
public void insertBoard(BoardVO boardVO) throws Exception
{
//insert 처리 구현
}
DAO에서 주의해야할 사항은 반드시 데이터베이스 관련 처리만 구현해야 해야 한다는 것이다. 간혹 개발하다보면 자신도 모르는 사이에 비즈니스 로직을 구현하는 경우가 있는데 이럴 경우에는 반드시 Control 클래스로 해당 로직을 이동시키는 리팩토링을 수행해야 한다.
많은 프로젝트들을 보면 목록 조회의 경우 return type으로 Vector, ArrayList 등과 같은 Collection을 사용하거나 심지어는 ResultSet을 사용하는 경우가 있다. 필자가 추천하는 것은 Value Object의 배열을 사용하는 방법이다.
앞의 소스에도 나타나 있지만 Collection을 사용하는 이유가 실제 데이터가 몇건인지 모르기 때문에 동적으로 객체를 저장하기 위한 방식을 선택했을 것이다. 하지만 이러한 Collection Type은 소스의 가독성에는 치명적이라 할 수 있다. 실제 Collection 내부에 어떤 객체가 존재하는지에 대해서는 소스를 보거나 문서로 남기는 방법밖에 없다.
지금과 같은 구조에서는 서브시스템 등 사이에서도 서로간의 기능을 호출하게 되어 있는데 인터페이스 정의서에 Collection Type으로 되어 있으면 인터페이스의 정의만으로는 부족해서 또 다른 설명이 필요하게 된다.
따라서 위의 소스에서 처럼 내부적으로는 Collection Type으로 관리한다 하더라도 외부에 보이는 함수의 정의에 대한 return type은 특별한 경우를 제외하고는 반드시 특정 클래스의 type으로 정의하는 것이 좋다.
소스의 가독성 얘기가 나왔으니 가독성에 대해 몇가지 얘기하면 필자의 경우 가독성을 높이기 클래스, 메소드, 변수의 명칭은 가능한 약어는 사용하지 않는다. 기존 C언어 개발에 익숙한 개발자들이 작성한 자바 소스를 보면 해당 변수가 어떤 용도로 사용하는지 파악하려면 변수의 이름만 가지고는 도저히 파악이 불가능하다. 실제 프로그램의 로직을 분석한 다음에서야 변수의 사용용도를 알게 되는데 자바에서는 이렇게 작성하지 않는다.
클래스명, 메소드명, 변수명이 길다고 해서 나쁜점은 타이핑을 좀 더 많이 해야 한다는 것 밖에 없다.
그리고 int a, b, c 와 같은 이런 극단적인 임시 변수를 사용하는 개발자들도 있는데 테스트성 코드라 할지라도 이러한 명칭은 사용하지 말라. 테스트 코드가 나중에는 실제 운영 코드가 바뀌는 수를 무수히 많이 보았다.
가독성 관련 또 다른 주제는 {의 위치이다. SUN에서의 자바 코딩 컨벤션에서는 {의 위치를 다음과 같이 작성하라고 권고하고 있다.
if( value == 10) {
.
} else if (value == 20) {
.
}
하지만 이렇게 구성하는 경우 블록의 시작과 끝을 알기가 너무 어려워 진다. 블록이 짧을 경우에는 모르겠지만 블록이 길거나 if절의 조건 비교가 길어질 경우 {의 위치가 너무 멀리 떨어져 있어 한눈에 블록이 들어오지 않는다. 또한 컴파일시 {와 관련된 에러가 발생한 경우 {} 쌍을 찾기가 어렵게 된다.
따라서 필자의 경우는 다음과 같이 작성한다.
if(value == 10)
{
.
}
else if(value == 20)
{
.
}
물론 클래스 선언, 메소드 선언 try..catch 절에서도 마찬가지 이다.
try
{
.
}
catch(Exception e)
{
.
}
{ 관련된 얘기를 한가지 더 해보자.
C언어 또는 자바언어에서는 한줄의 블록은 다음과 같이 {}를 생략해도 가능하도록 구성되어 있다.
if(value == 10)
System.out.println(한줄짜리 블록);
하지만 이렇게 사용했을 때에도 역시 소스의 가독성이 많이 떨어지게 된다. 들여쓰기가 조금 잘못된 경우 다음과 같이 블록문에 포함된 것 처럼 보일수도 있게 된다.
if(value == 10)
System.out.println(한줄짜리 블록);
System.out.println(여기는 블록이 아님);
System.out.println(여기는 블록이 아님); 라인은 실제 if 절의 블록에 속하지 않는다. 하지만 언뜻보기에는 if절의 블록에 포함된 것처럼 보이고 있다. 따라서 한줄짜리 블록의 경우 다음 두가지 경우로 사용하는 것이 가독성 측면에서는 좋다고 할 수 있다.
if(value == 10) System.out.println(한줄짜리 블록);
또는
if(value == 10)
{
System.out.println(한줄짜리 블록);
}
너무 다른 방향으로 흘러간 것 같은데 다시 본론으로 돌아와서 DAO에서 마지막으로 주의해야할 사항은 앞의 소스에서처럼 SELECT 문 등에서 SELECT *INSERT  VALUE() 등과 같이 특정 컬럼명을 명시하지 않는 SQL문은 사용하지 말라는 것이다.
앞의 예제의 경우 라인수를 줄이기 위해 SELCT * FROM T_BOARD 등과 같이 작성하였는데 실제는 다음과 같이 모든 컬럼명을 주는 습관을 들여야 한다.
SELECT BOARDID, TITLE. FROM T_BOARD
 VO(Value Object)
- Model, View 간의 데이터 전달용도
- 멤버변수와 getter, setter 메소드로 구성
- 기존 JSP의 Baen과 동일
- 파라미터가 길어질 경우 VO를 이용하는 것이 효율적
- Entity VO와 Custom VO 두 종류
. Entity VO : Entity(EJB 또는 DB) 정보와 동일
. Custom VO : 사용자 필요에 의해 만든 VO
- 자동화 도구를 사용하여 만드는 것이 편리함
[소스] Value Object
public class BoardVO
{
String boardId ;
String title ;
String contents ;
String userId ;
String userName ;
int readCount;
String writeDate;
public String getBoardId () { return this.boardId ; }
public String getTitle () { return this.title ; }
public String getContents () { return this.contents ; }
public String getUserId () { return this.userId ; }
public String getUserName () { return this.userName ; }
public int getReadCount() { return this.readCount ; }
public String getWriteDate() { return this.writeDate ; }
public void setBoardId (String boardId ) { this.boardId = boardId ; }
public void setTitle (String title ) { this.title = title ; }
public void setContents (String contents ) { this.contents = contents ; }
public void setUserId (String userId ) { this.userId = userId ; }
public void setUserName (String userName ) { this.userName = userName ; }
public void setReadCount(int readCount) { this.readCount = readCount; }
public void setWriteDate(String writeDate) { this.writeDate = writeDate; }
}
Value Object는 Presentation, Business, Data 각 Layer 간의 데이터 전달을 위해 사용된다. Value Object는 멤버변수와 멤버변수의 값을 설정하고 가져오기 위한 setter, getter 메소드만 존재한다.
Insert, Update 등의 처리를 위한 Value Object는 DB의 테이블 컬럼과 일치하게 구성하는 것이 일반적이고 Insert 처리시에 InsertCommand에서 request.getParameter()를 이용하여 SUBMIT 된 파리미터를 추출하여 Insert 처리할 Value Object 객체를 생성하여 Control의 Insert 처리 메소드의 파라미터로 전송한다.
조회시에는 Control에서 Value Object를 반환하게 되는데 이때에는 반드시 DB의 테이블 컬럼과 일치하지 않기 때문에 화면에 나타나는 내용에 따라 적절하게 Value Object를 새로 생성해 주면 된다.
여기까지의 설명으로 필자가 제시하는 아키텍처를 구성하는 각 구성요소에 대해 설명하였다. 좀 더 자세한 사항은 설명보다는 실제 소스를 보거나 실제 간단한 게시판만이라도 구현해 보는 것이 이해하는데 도움이 될 것이다.
다음은 JSP Model 2를 이용하여 시스템을 구축하는 경우의 주의사항에 대해 몇가지 살펴보자.
 Reload 버튼 클릭시 처리 방법
JSP Model2 구조는 기본적으로 Front Controller 구조를 취하고 있는데 Front Controller구조를 사용할 경우 브라우저의 Reload 버튼이 문제가 된다.
사용자는 Browser에서 등록할 내용을 입력하고 submit 처리를 통해 서버로 request를 보내게 된다. 그러면 Front Controller는 이를 받아 처리할 Command의 execute() 메소드를 수행한 다음, 이동할 페이지로 해당 request에 대해 forward 시킨다. 이러한 상태에서 사용자의 브라우저에 나타나는 화면에서는 이전의 request가 유효한 상태로 남아 있게 되고 사용자가 입력처리후 나타나는 화면(일반적으로는 목록 화면)에서 Browser의 Reload 버튼을 누를 경우 동일한 이전의 입력처리 요청과 동일한 request가 서버로 전송되어 입력처리가 한번 더 발생하게 된다.
Key 값이 중복되는 경우 Key duplication 에러가 발생할 것이고 Key를 시간 또는 일련번호로 생성하는 경우 동일한 데이터가 한건 더 등록되게 된다.
이러한 문제점을 해결할 수 있는 방법은 두가지 정도 있는데 첫번쨰는 Session을 이용하는 방법이고 두번째는 입력처리 후 다음 페이지로 이동할 때 임시 페이지를 호출하고 임시 페이지에서 원래의 목적된 페이지로 이동시키는 기능을 수행하게 하면 된다.
Session을 이용한 방법은 각각의 입력화면이 호출될 때 입력 화면 호출을 처리하는 Command(TechTipInsertViewCommand.java)에서 Session Id, 현재시간을 이용하여 SESSION에 설정한 다음 입력화면을 호출한다.
입력화면에서는 해당 SESSION의 값을 이용하여 hidden 필드로 설정한 다음 입력 SUBMIT 요청시 같이 전송하게 된다.
입력처리를 담당하는 InsertCommand에서는 해당 request에서 넘겨온 값과 SESSION의 값이 일치하는지 판단하여 일치할 경우 Insert 처리를 하고 null이거나 일치하지 않을 경우 처리를 하지 않는다. 처리후 SESSION 값을 Clear 처리한다.

[그림] Session을 이용한 Reload 처리 방지
필자가 사용한 방법의 단점은 각 입력 화면별로 다른 SESSION ID를 가져야 하며 하나의 사용자가 동일한 입력 화면을 동시에 두개의 브라우저에서 호출하여 입력하는 경우 첫번쨰 호출한 입력화면에서 등록한 내용은 입력되지 않는다.
첨부된 실제 구현 사례(publicProject) 소스에서 TechTip 부분에서 이러한 방식이 사용되었다.
임시 페이지를 이용하는 경우 모든 경우에 임시 페이지를 작성하는 경우 개발 시 불편하기 때문에 다음과 같이 공통적으로 사용할 페이지를 만든 다음 이 페이지로 목적 페이지를 호출하기 위한 정보를 넘겨주어 원하는 페이지로 이동시키면 된다. 물론 여기서도 직접 해당 페이지로 바로 이동 시키는 것이 아니라 FrontController를 거쳐 이동시키게 된다.
다음은 MoveView.jsp에 대한 소스이다.
[소스] MoveView.jsp
<%@ page contentType="text/html;charset=EUC-KR"%>
<%@ page import="home.common.*"%>
<%@ page import="java.util.*"%>
<%
String message = (String)request.getAttribute("message");
if(message == null) message = "";
%>
<HTML>
<HEAD>
<SCRIPT language="javascript">
function form_submit(message)
{
if(message != "")
{
alert(message);
}
document.FORM_TEMP.target = "";
document.FORM_TEMP.submit();
}
</SCRIPT>
</HEAD>
<BODY onLoad="form_submit('<%=message%>');">
<FORM name="FORM_TEMP" method="POST"
action="<%=request.getAttribute("FORM_ACTION")%>">
<%
Enumeration attNames = request.getAttributeNames();
String name = "";
while(attNames.hasMoreElements())
{
name = (String)attNames.nextElement();
if("FORM_ACTION".equals(name)) continue;
%>
<INPUT type="hidden" name="<%=name%>" value="<%=request.getAttribute(name)%>">
<%
}
%>
</FORM>
</BODY>
</HTML>
해당 MoveView.jsp 를 이용하기 위한 Command의 구현 내용이다.
request.setAttribute("boardType", boardVO.getBoardType());
request.setAttribute("command", "BoardListView");
request.setAttribute("FORM_ACTION", "/board");
return MoveView.jsp;
게시판 등록 후 게시판 목록 조회로 이동하기 위해 목록 조회에 필요한 파라미터(boardType, command)와 수행할 FrontController(/board)를 명시해 주었다.
첨부된 실제 구현 사례(publicProject) 소스에서 게시판 및 추천도서에서 이러한 방식이 사용되었다.
 Transaction 제어에 대한 Role
Transaction은 흔히 DB의 2phase commit을 생각하면 된다. Master-Detail 관계가 있는 두개의 테이블에 데이터를 저장할 때 두 개의 테이블 모두에게 정상적으로 저장되는 것이 보장하도록 처리하는 것이다.
Transaction 제어에 대해서는 여기서도 여전히 골치거리이다. EJB를 사용하는 경우 EJB Container가 내부적으로 알아서 transaction 처리를 해주지만 그렇지 않은 경우에는 개발자가 이러한 transaction 처리를 직접 해주어야만 한다.
특히 Connection Pool을 사용하는 경우 transaction 처리와 관련된 Connection 클래스의 AutoCommit 모드와 관련된 사항을 제대로 사용하지 않게 되면 DB서버에 Lock이 걸리게 되고 이러한 Lock가 많이 발생하게 되면 결국 시스템은 멈추게 된다. 이것은 일반적인 JDBC를 이용하여 transaction 제어를 할 때 주의사항이고 여기서는 이것과 다른 측면에서 살펴보자.
데이터베이스에 대한 Query의 수행은 Data Access Object에서 처리하도록 아키텍처를 정의하였는데 그러면 데이터베이스와 관련된 transaction에 대한 제어는 어느 Layer에서 처리하는 것이 합당한가 하는 문제이다.
필자의 경우 Control에서 처리하고 있는데 이렇게 구성하다 보니 Control에서 Connection을 가져와서 AutoCommit 모드를 변경해주고 DAO에 다시 이 Connection을 넘겨야 한다. 또한 처리 완료 후 다시 Commit 또는 Rollback 처리를 한 다음 AutoCommit 모드를 원래대로 복구하고 Connection을 close 한다.
이러한 일련의 로직이 DB와 관련된 사항이기 때문에 DAO에 두어야 한다는 의견도 있다. 필자가 Control에 이러한 로직을 구현한 이유는 transaction을 비즈니스 로직을 보았기 때문이다. 예를 들어 게시판에 글을 등록하면서 첨부정보를 같이 등록하는 경우 이것은 비즈니스 로직이기 때문이다(물론 아니라고 반론을 제기하는 독자도 있겠지만).
이글을 읽는 독자들도 나름대로 필자가 제시한 방안으로 구현해 보면 이러한 문제를 한번쯤은 고민하게 될 것이다. 물론 정답은 없다.
 JNDI를 이용하여 Data Source를 가져오는 경우
Servlet Container에서 JDBC를 이용하여 데이터베이스 처리를 하는 경우 기존에는 개발자가 직접 Connection Pool에 관련된 사항까지 모두 개발을 해야했지만 요즘은 Container 자체에서 제공하는 Pool을 JNDI를 이용하여 가져오고 있다.
이렇게 JNDI를 이용하여 Connection을 가져와 처리하는 경우 위와 같은 클래스들의 재활용을 염두해둔 아키텍처에서는 JNDI 부분까지 고려하여 구성을 하여야 한다. JNDI Lookup이 가능한 경우는 해당 서비스가 특정 Container 상에서 운영되어야만 Lookup이 가능하기 때문에 위의 아키텍처 구조에서 Control 클래스에서 Connection을 가져오거나 DAO에서 Connection을 가져오게 하는 경우 Container 상에서 운영되지 않는 batch 프로그램에서 해당 DAO의 기능을 이용할 경우 JNDI Lookup이 되지 않기 때문에 사용하지 못하게 된다. 따라서 이렇게 JNDI Lookup을 활용하지 못하는 상황에서도 사용을 하기 위해서는 Connection을 상황에 따라 적절하게 가져오는 유틸리티성 클래스가 필요하게 된다. 간단하게 Properties를 파라미터로 넘겨 가져오게 하거나 JNDI Lookup이 실패하는 경우 기존의 Driver loading 방식을 이용하는 가져오는 방법 등 구현 방법은 다양하기 때문에 별도로 소스를 첨부하지는 않았다.
하지만 이러한 고려사항이 있다는 것을 생각하고 시스템을 구성하는 것이 도움이 될 것이다.
5. 결론
지금까지 JSP Model2에 대한 설명 및 구현 사례에 대해서 살펴보았다.
대부분의 개발자들은 앞부분에서 나온 설명보다는 소스를 통해 JSP Model2 구조에 대해서는 어느 정도 이해했으리라 생각한다. 여기서는 정리하는 차원에서 앞 부분에서 그냥 지나쳤을 것 같은 내용을 이제 구조가 이해가 되는 상태에서 다시 살펴보도록 하자.
먼저 JSP Model2 구조를 사용하는 경우 소스 코드의 중복이 없어 진다고 하였는데 앞에서의 설명한 내용중 Control 부분에서 하나의 시스템이라 할지라도 각 서브시스템을 별도의 시스템으로 간주하고 다른 서브시스템의 기능을 사용하기 위해서는 해당 서브시스템에서 직접 DB를 접근하는 방식을 취하지 않고 해당 기능을 수행하는 서브시스템의 Control 클래스에 있는 메소드를 호출하게 구성해야 한다고 했다.
이렇게 함으로써 코드의 중복이 사라질 수 있는 것이다. 하지만 이것은 전적으로 개발자에게 많이 의지하는 부분이다. 개발자가 이러한 개념에 대해 정확히 이해하고 다른 서브시스템에 있는 기능을 자신이 개발하고 있는 서브시스템내에서 함부로 구현해서는 안된다. 이 부분에 대해서는 품질을 관리/감독하는 역할을 수행하는 팀원이 꾸준히 코드 리뷰를 통해 개발자들에게 주지시켜야 한다.
두번째 문제로 개발자들 각 개인의 아키텍처로 많이 구성되기 때문에 문제가 발생한다고 하였는데 아키텍처를 도입함으로써(본 사례에서는 JSP Model2) 어떤 개발자가 개발을 수행하더라도 동일한 구조의 소스가 생성되기 때문에 프로젝트를 수행하는 개발자들 사이의 커뮤니케이션도 원할해지고 유지보수 이관시 별도의 코드에 대한 이해는 필요가 없게 된다.
필자의 경험으로 이러한 아키텍처를 기반으로 시스템의 개발을 진행할 경우 주기적인(개발 초기에는 주 2 ~ 3회정도, 개발 진행 중에는 주 1회 정도)으로 해당 아키텍처에 대해 모두 이해를 하고 있는 품질 담당자가 코드 리뷰를 실시하는 것이 좋다. 코드 리뷰에서는 각 Layer 별로 정해진 클래스의 역할대로 개발이 진행되고 있는지에 대한 검증과 다른 서브시스템의 기능을 타 서브시스템에서 개발하고 있는 것이 없는 지를 파악해야 한다.
또한 이러한 구조에 익숙하지 않는 개발자들이 프로젝트에 투입되었을 때에는 개발 초기에는 집중적으로 관심으로 가져야 한다. 기존 JSP Model 1 구조에 익숙한 개발자들은 자신도 모르는 사이 또는 의도적으로 JSP에서 SQL문을 수행하는 등의 코드를 생산하기 때문이다.
이러한 소스 코드의 검증을 위해서는 소스코드의 공유가 필수적인데 CVS, PVCS 등 소스 버전관리 툴을 이용하여 프로젝트를 추진하는 것이 많은 도움이 될 것이다.
마지막으로 본 글에서 제시한 아키텍처 및 기법들은 필자의 의견임을 다시 한번 밝혀 두고자 한다. 그리고 처음부터 객체지향 분석방법으로 공부하는 독자들은 이번 글을 에서 나온 내용은 참고로만 하기 바란다. 본 글은 기존의 구조화된 분석/설계/구현에 익숙한 개발자들에게 어떻게 하면 쉽게 객체지향으로 넘어갈수 있을까 하는 고민에서 나온 아키텍처이기 때문이다.
따라서 필자가 제시한 아키텍처의 개념이 이해가 되고 몇번 구현해본 독자들이라면 Entity가 메인이 되는 설계 및 아키텍처로 구성해야하는 것이 좋다.
또 한가지 당부사항은 필자가 이러한 구조에 대해 현재의 단순한 JSP 위주의 개발 및 유지보수를 진행하고 있는 개발자를 대상으로 몇번의 설명을 가져본 결과 대부분의 개발자는 거부감을 가지고 접근을 하고 있는 것을 볼 수가 있었다. 파일 하나만 만들면 되는 것을 무었 때문에 몇 개의 파일을 만들고 다시 컴파일하고 하는 복잡한 단계를 거치냐 하는 것이다.
필자가 단호하게 말할 수 있는 것은 한번 해본 다음에도 평가하라는 것이다. 왜 요즘 모든 책이며 잡지에 CBD, 객체지향, 패턴, 아키텍처라는 용어들이 난무하는지를 생각해보아야 할 것이다. JSP Model2는 이러한 기술로의 첫걸음이라고 생각하면 될 것이다.
변화를 두려워하는 개발자가 되지 않기를 바라며 이 글을 읽은 독자들의 생각의 방식이 바뀌는 계기가 되었으면 하는 것이 필자이 바램이다.
[출처] Jsp Model 2|작성자 hny76

댓글

이 블로그의 인기 게시물

[메모] PostgreSQL에서 Insert 하는 경우 자동채번 PK가 중복에러 나는 경우

[C# & LINQ] 랜덤으로 데이터를 한 개 추출하는 방법

[react-native] uuid 생성이 에러가 날 때 대처법