- ···
- 18.
[멀티캠퍼스] 풀스택 개발자 아카데미 (18) - Servlet/JSP(2) - 19.[멀티캠퍼스] 풀스택 개발자 아카데미(19) - Servlet/JSP(3)현재
- 20.
[멀티캠퍼스] 풀스택 개발자 아카데미 (20) - Servlet/JSP(4) - ···
1. 스파게티 코드 벗어나기
지난 글에서는 클라이언트의 요청과 서버의 응답을 처리하는 기본적인 방법과 함께, JSP가 모든 역할을 담당하는 MVC Model 1 아키텍처를 살펴보았다. Model 1은 간단하고 빠르게 개발할 수 있다는 장점이 있지만, JSP 파일 하나에 화면을 그리는 코드(View)와 데이터를 처리하는 코드(Controller)가 뒤섞여 프로젝트 규모가 커질수록 유지보수가 극도로 어려워지는 '스파게티 코드' 문제를 야기한다.
이번 글에서는 이러한 Model 1의 한계를 극복하고, 현대적인 웹 애플리케이션 개발의 근간이 되는 MVC Model 2 아키텍처에 대해 다룬다. Model 2의 핵심 철학인 **'역할의 분리'**가 왜 중요한지 이해하고, 이를 효율적으로 구현하기 위한 프론트 컨트롤러(Front Controller) 패턴과 커맨드(Command) 패턴을 알아보고, 컨트롤러에서 뷰로 제어를 넘기는 두 가지 핵심 방법인 포워딩(Forward)과 리다이렉트(Redirect)의 차이점을 알아본다.
2. MVC Model2 Architecture
Model 1 구조의 가장 큰 문제점은 디자이너와 개발자의 작업 영역이 불분명하다는 것이다. 디자이너는 HTML 구조 안의 복잡한 자바 코드를 신경 써야 하고, 개발자는 자바 코드 사이사이에 섞인 HTML 태그를 수정해야 한다. 이러한 비효율을 해결하기 위해 각 구성 요소의 역할을 명확하게 나눈 MVC Model 2 방식이 표준으로 자리 잡게 되었다.
MVC Model 2는 애플리케이션을 다음의 세 가지 핵심 컴포넌트로 분리한다.
- Model (모델): 애플리케이션의 데이터와 비즈니스 로직을 담당한다. 데이터베이스와 상호작용하는 DAO(Data Access Object)나 데이터를 담는 VO(Value Object) / DTO(Data Transfer Object)가 여기에 해당한다. 모델은 뷰나 컨트롤러에 대해 알지 못하며, 오직 자신의 데이터와 로직 처리에만 집중한다.
- View (뷰): 사용자에게 보여지는 UI(사용자 인터페이스)를 담당한다. Model 2 구조에서 JSP는 순수하게 뷰의 역할에만 집중한다. 컨트롤러로부터 전달받은 데이터를 화면에 표시하는 역할만 수행하며, 복잡한 비즈니스 로직을 포함하지 않는다.
- Controller (컨트롤러): 모델과 뷰 사이의 상호작용을 조정하는 '교통 경찰' 역할을 한다. 사용자의 요청을 가장 먼저 받아, 해당 요청을 처리하는 데 필요한 모델을 호출한다. 모델이 작업을 완료하면, 그 결과를 보여줄 뷰를 선택하여 제어를 넘긴다. Model 2에서 이 역할은 주로 **서블릿(Servlet)**이 담당한다.
MVC Model 2는 역할을 분리함으로써, 개발자는 데이터 처리 로직에, 디자이너는 화면 구성에만 집중할 수 있게 되어 협업이 용이해졌다. 또한, 각 컴포넌트의 독립성이 높아져 코드의 재사용성과 테스트 용이성이 향상되고, 유지보수가 훨씬 수월하다.
3. 프론트 컨트롤러 패턴
MVC Model 2 구조를 적용하기로 했다고 가정한다. 회원 목록 조회는 MemberListServlet, 회원 가입 처리는 MemberJoinServlet, 정보 수정은 MemberUpdateServlet... 이렇게 모든 기능마다 서블릿을 하나씩 만들게 된다. 이는 기능이 수십, 수백 개로 늘어났을 때, 서블릿 클래스 관리와 URL 매핑이 걷잡을 수 없이 복잡해질 것이다.
이러한 문제를 해결하기 위해 프론트 컨트롤러(Front Controller) 패턴을 사용한다. 이 패턴은 모든 클라이언트의 요청을 단 하나의 대표 서블릿에서 받아서 처리하는 중앙 집중형 설계 방식이다.
예를 들어, .do로 끝나는 모든 요청을 하나의 FrontController 서블릿이 받도록 web.xml이나 어노테이션에 URL 패턴을 /*.do와 같이 설정한다. 그 후 FrontController는 요청된 URI(request.getRequestURI())를 분석하여 사용자가 어떤 기능을 원하는지 파악하고, 그에 맞는 실제 처리 담당자에게 작업을 위임한다. 이렇게 하면 애플리케이션의 진입점이 하나로 통일되어 공통 로직(인코딩 설정, 인증 체크 등)을 처리하기 용이하고, 전체 구조가 깔끔해진다.
4. 커맨드 패턴
프론트 컨트롤러가 모든 요청을 받는다고 해서, 그 안에서 모든 작업을 if-else 문으로 분기하여 직접 처리하는 것은 좋은 방법이 아니다. 이는 결국 프론트 컨트롤러 자체를 또 다른 '스파게티 코드'로 만드는 길이 될 수 있다.
이때 커맨드(Command) 패턴을 함께 사용하면 프론트 컨트롤러의 부담을 덜고 역할을 명확히 분리할 수 있다. 커맨드 패턴은 각 요청에 대한 처리를 별도의 '커맨드' 클래스로 캡슐화하는 방법이다.
- 모든 커맨드 클래스가 구현할 공통 인터페이스(예:
Command)를 정의한다. 이 인터페이스에는 보통execute()와 같은 추상 메소드가 포함된다. - '회원 목록 조회', '회원 가입' 등 각 기능에 해당하는 구체적인 커맨드 클래스(예:
MemberListCommand,MemberJoinCommand)를 작성하고,Command인터페이스를 구현하게 한다. - 프론트 컨트롤러는 요청 URI에 따라 어떤 커맨드 클래스를 실행해야 할지 판단하고, 해당 클래스의 인스턴스를 생성하여
execute()메소드를 호출하기만 하면 된다.
이렇게 하면 각 기능의 로직이 독립적인 클래스로 분리되어 재사용 및 테스트가 용이해지고, 새로운 기능을 추가할 때도 프론트 컨트롤러의 코드를 수정할 필요 없이 새로운 커맨드 클래스만 추가하면 되므로 확장성이 매우 좋아진다.
5. 제어권 넘기기: Forward vs Redirect
Controller(Servlet)이 커맨드를 통해 모델(DAO)의 작업을 끝내고 나면, 이제 그 결과를 사용자에게 보여줄 뷰(JSP)로 제어권을 넘겨야 한다. 이때 사용하는 대표적인 방법이 포워드와 리다이렉트이다.
5.1. 포워드(Forward)
포워드는 서버 내부에서 요청을 다른 리소스(Servlet이나 JSP)로 전달하는 방식이다.
포워드는 사내 대리 전달과 같다. 고객이 1번 창구에 서류를 제출했지만, 실제 처리는 2번 창구에서 해야 할 때, 1번 창구 직원이 서류를 직접 2번 창구로 가져다주는 것과 같다. 고객(클라이언트)은 자신이 다른 창구로 이동했다는 사실을 모르고, 계속 1번 창구 앞에 서 있게 된다.
RequestDispatcher 객체를 통해 이루어진다.
특징:
- 서버 내에서만 이동하므로 클라이언트의 웹 브라우저 URL은 최초 요청 주소 그대로 바뀌지 않는다.
- 최초의
request와response객체가 그대로 유지되어 전달된다. 따라서request.setAttribute()로 담은 데이터를 JSP에서${...}(EL)나<%= ... %>(표현식)을 통해 꺼내 쓸 수 있다. - 주로 조회(SELECT) 요청과 같이, 컨트롤러가 처리한 데이터를 뷰에 전달하여 화면을 구성할 때 사용된다.
5.2. 리다이렉트 (Redirect)
리다이렉트는 서버가 클라이언트의 브라우저에게 이 주소로 다시 요청하라고 명령을 내리는 방식이다.
리다이렉트는 번호표 재발행과 같다. 고객이 1번 창구에 갔더니 "이 업무는 2번 창구 소관입니다. 저쪽으로 가서 다시 번호표 뽑고 요청하세요."라고 안내하는 것과 같다. 고객(클라이언트)은 안내받은 2번 창구로 직접 이동하여 완전히 새로운 요청을 시작한다.
HttpServletResponse 객체의 sendRedirect() 메소드를 사용한다.
특징:
- 서버는 브라우저에게 302 응답 코드와 함께 새로운 URL을 보낸다. 브라우저는 이 URL로 완전히 새로운 요청을 다시 보낸다.
- 브라우저의 주소창 URL이
sendRedirect()에 지정된 주소로 변경된다. - 완전히 새로운 요청이므로, 이전 요청의
request와response객체는 소멸된다. 따라서request.setAttribute()로 데이터를 전달할 수 없다.
5.2.1. 리다이렉트의 사용
리다이렉트는 일반적인 상황보다는, 회원 가입, 정보 수정, 글쓰기 등 서버의 데이터에 변화를 주는 POST 방식의 요청을 처리한 후에 사용한다. 만약 이때 포워드를 사용하면, 사용자가 결과 페이지에서 '새로고침(F5)'을 눌렀을 때 브라우저는 마지막 요청(POST 요청)을 다시 보내게 되어 동일한 데이터가 중복으로 등록되는 심각한 문제가 발생할 수 있다. 리다이렉트를 사용하면 '새로고침' 시 마지막 요청인 GET 방식(목록 페이지 조회)이 다시 전송되므로 이러한 문제를 방지할 수 있다. 이를 PRG(Post-Redirect-Get) 패턴이라고 한다.
6. MVC Model 2 기반 회원 목록 조회
이제 배운 개념들을 종합하여 MVC Model 2, 프론트 컨트롤러, 커맨드 패턴을 적용한 간단한 회원 목록 조회 기능을 구현해볼 수 있다.
6.1. Command.java (인터페이스) 작성
6.2. MemberListCommand.java (클래스) 작성
- 참고:
MemberVO:
6.3. FrontController.java (서블릿) 작성
6.4. memberList.jsp 작성
6.5. 동작
브라우저에서 http://localhost:8080/<프로젝트명>/memberList.do로 요청하면, FrontController가 요청을 받아 MemberListCommand를 실행하고, DAO로부터 받은 회원 목록 데이터를 request에 담아 memberList.jsp로 포워딩하여 최종 결과 화면을 보여준다.
7. 결론
이번 편에서는 MVC Model 1의 한계를 극복하기 위한 대안으로, 역할과 책임을 명확히 분리하는 MVC Model 2 아키텍처를 살펴보았다. 또한, 모든 요청을 중앙에서 관리하는 프론트 컨트롤러 패턴과 각 요청의 처리를 독립적인 클래스로 캡슐화하는 커맨드 패턴을 적용하여 보다 체계적이고 확장 가능한 구조를 만드는 방법을 학습했다.
직접 구현한 프론트 컨트롤러와 커맨드 패턴은 Model 1에 비해 월등히 구조적이고 유지보수가 용이하지만, 여전히 요청 URI와 커맨드 클래스를 맵핑하는 if-else 블록과 같은 반복적인 코드를 개발자가 직접 작성해야 하는 번거로움이 남아있다.
이는 자연스럽게 다음 단계로 우리를 이끈다. 바로 이러한 기반 구조를 모두 자동화하고, 개발자가 오직 비즈니스 로직에만 집중할 수 있도록 도와주는 강력한 도구인 Spring 프레임워크이다.
다음 글에서는 Spring 프레임워크에 앞서, JDBC를 사용한 기본 DB 연결 방식의 문제점을 알아보고, 이를 효율적으로 관리하기 위한 Connection Pool의 개념과 도입 방법에 대해 다뤄볼 예정이다.