본문 바로가기
Spring/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

MVC 패턴을 적용해서 회원 관리 웹 만들기

by hk27 2022. 1. 21.
비즈니스 로직과 뷰 로직을 분리하면 어떨까요?

 

안녕하세요.

이번 게시글에서는 MVC 패턴을 적용해서 회원 관리 웹을 만들어보겠습니다.

 

서블릿과 JSP의 한계

앞서 서블릿(https://passionate.tistory.com/37)과 JSP(https://passionate.tistory.com/38)로 회원 관리 웹을 만들어보았습니다. 궁금하신 분은 게시글을 참고해주세요!

두 방법으로 웹을 만들었을 때, 한 파일에 많은 역할이 들어간다는 문제가 있었습니다.

웹을 만들 때는 비즈니스 로직과 뷰 로직이 필요합니다. 

비즈니스 로직은 유저의 요청에 따른 결과물을 만들어내기 위한 일련의 작업을 의미합니다[1]. 로그인을 예로 들면, 유저가 아이디와 비밀번호를 입력하면 유저에게는 단순하게 로그인이 되었는지 아닌지만을 출력하지만, 내부적으로 DB에 접근해서 id와 비밀번호 정보가 일치하는지 확인합니다. 다시 정리하자면, 사용자가 어떤 요청을 했을 때, 그 요청을 처리하기 위해 내부적으로 진행되는 절차를 비즈니스 로직이라고 합니다. 

뷰 로직은 클라이언트가 보는 화면을 위한 HTML을 만드는 작업입니다.

 

서블릿과 JSP는 한 파일에 비즈니스 로직과 뷰 로직이 한 파일 내에서 처리됩니다. 

하나의 서블릿과 JSP가 너무 많은 역할을 하게 되고, 결과적으로 유지보수가 어려워집니다.

일부 비즈니스 로직을 수정해야 하는데 파일에 뷰 로직이 함께 있으면 수정이 어려울 것입니다.

심지어 비즈니스 로직과 뷰 로직은 변경의 생명 주기가 다릅니다. 

비즈니스 로직을 수정하는 일과 비즈니스 로직을 수정하는 일이 다른 시점에 발생할 가능성이 크고, 서로에게 큰 영향을 주지도 않습니다.

이렇게 변경의 생명 주기가 다른데 하나의 코드로 관리하면 유지 보수하기 좋지 않습니다.

 

MVC 패턴

따라서 비즈니스 로직과 뷰 로직을 분리하게 되었고, 그것이 바로 MVC 패턴입니다.

MVC(Model View Controller) 패턴은 컨트롤러(Controller)와 뷰(View) 영역으로 역할을 나눈 것을 말합니다.

대부분의 웹 애플리케이션이 MVC 패턴을 사용합니다.

컨트롤러는 HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행합니다. 뷰에 전달할 데이터는 모델에 담습니다.

모델은 컨트롤러에서 데이터를 받아서 뷰에 전달합니다.

는 모델에 담겨있는 데이터를 사용해서 HTML을 생성해 화면을 그립니다.

 

MVC 패턴을 적용한 웹의 동작 과정은 아래와 같습니다. 

 

1. 클라이언트의 요청을 바탕으로 컨트롤러가 호출됩니다.

2. 컨트롤러는 파라미터를 검증하고, 비즈니스 로직을 실행합니다. 

3. 컨트롤러는 비즈니스 로직 수행 결과를 모델에 담습니다.

4. 컨트롤러가 뷰를 부릅니다.

5. 뷰는 모델의 데이터를 참조하며 HTML 화면을 그리는 뷰 로직을 수행합니다.

6. 클라이언트에 응답을 보냅니다.

 

 

다음으로 MVC 패턴을 적용해서 회원 관리 웹을 만들어봅시다.

서블릿을 컨테이너로 사용하고, JSP를 뷰로 사용할 것입니다.

회원을 저장하고, 저장된 회원 목록을 조회하는 기능을 구현해봅시다.

 

회원 등록

회원 등록 - 컨트롤러

먼저 회원 등록 폼을 만들어봅시다.

서블릿을 사용한 컨트롤러 코드입니다.

@WebServlet(name="mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

RequestDispatcher가 눈에 띕니다. 단어 그대로 요청을 보내는 객체입니다.

forward 메소드로 viewPath에 해당하는 jsp파일로 포워딩(화면 이동)합니다. 

 

회원 등록 - 뷰

JSP를 이용한 뷰 코드입니다.

<%@ page contentType = "text/html; charset=UTF-8" language = "java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="save" method="post">
    username=<input type="text" name="username" />
    age: <input type="text" name="age" />
    <button type="submit">전송</button>
</form>
</body>
</html>

 

뷰 코드에서 자바 코드 없이 html만을 작성하니 훨씬 깔끔하고 편리합니다. 

코드를 수행하면 아래와 같은 결과가 나옵니다. 

 

 

회원 저장

회원 저장 - 컨트롤러

회원을 저장하는 서블릿 컨트롤러 코드입니다.

@WebServlet(name="mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);
        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

 

컨트롤러는 서블릿으로, 뷰는 JSP로 구현했는데 모델은 어떻게 구현할까요?

 

HttpServletRequest를 모델로 사용합니다.

HttpServletRequest의 setAttrubite() 메소드로 컨트롤러에서 데이터를 전송하고, getAttribute() 메소드로 뷰에서 데이터를 받습니다. 

12번째 줄의 request.setAttribute("member", member) 는 "member"라는 이름으로 member 객체를 전송하는 것입니다.

뷰에서 member 객체를 사용할 수 있습니다. 

 

회원 저장 - 뷰

<%@ page contentType = "text/html; charset=UTF-8" language = "java" %>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
성공
<ul>
    <li>id=${member.id}</li>
    <li>username=${member.username}</li>
    <li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>

9~11번째 줄을 보시면 ${} 문법을 사용하고 있습니다.

JSP의 ${} 문법은 request의 attribute에 담긴 데이터 조회를 지원합니다.

${member.id} 는 위에서 담은 "member" 객체의 id 필드 값을 조회합니다.

Member 클래스의 id 필드는 private인데, 프로퍼티 접근법(객체.필드처럼 온점으로 접근)으로 조회할 수 있습니다. 

뷰에서 객체를 가져올 때는 getAttribute() 메소드를 사용하지 않고도 편하게 ${} 문법으로 데이터를 사용할 수 있습니다.

회원을 저장하면 페이지는 아래와 같이 나옵니다. 

 

회원 목록 조회

회원 목록 조회 - 컨트롤러

저장한 회원을 모두 조회하는 컨트롤러 코드입니다.

@WebServlet(name="mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();
        request.setAttribute("members",members);

        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

모든 회원을 List로 받아서 모델에 데이터를 담았습니다.

 

회원 목록 조회 - 뷰

회원 목록을 조회하는 뷰입니다.

<%@ page contentType = "text/html; charset=UTF-8" language = "java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
    <thead>
    <th>id</th>
    <th>username</th>
    <th>age</th>
    </thead>
    <tbody>
    <c:forEach var="item" items="${members}">
        <tr>
            <td>${item.id}</td>
            <td>${item.username}</td>
            <td>${item.age}</td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

17~23번째 줄에서 forEach로 member를 순서대로 꺼내서 item 변수에 담고 출력합니다.

17번째 줄의 <c:forEach>는 List, 배열 요소를 순서대로 반복해서 처리할 수 있는 태그입니다.

이 기능을 사용하려면 2번째 줄처럼 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>를 선언해야 합니다. 

 

결과는 아래와 같습니다.

 

정리

이번 게시글에서는 MVC 패턴을 적용해서 회원 관리 웹을 만들었습니다.

컨트롤러와 뷰를 분리하니 코드가 훨씬 깔끔해졌습니다.

그러나 코드의 중복이 많다는 단점이 있습니다. 

dispatcher 객체로 forward하는 코드, viewPath의 prefix와 suffix 등이 중복되어있습니다.

그리고 모든 컨트롤러에 HttpServletRequest request, HttpServletResponse response가 있는데, request는 사용할 때도 있고, 사용하지 않을 때도 있으며 response는 거의 사용하지 않습니다. 

불필요한 코드는 줄일 수 있겠죠. 

 

모든 요청에 대해서 공통 처리를 하는 기능이 있으면 반복되는 코드를 줄일 수 있을 것입니다.

다음 게시글에서는 수문장 역할을 하는 프론트 컨트롤러(Front Controller) 패턴을 도입해서 공통 처리를 하는 방법을 알아보겠습니다. 

 

 

인프런  '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고 자료

[0] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술, 섹션 3. 서블릿, JSP, MVC 패턴 https://www.inflearn.com/course/스프링-mvc-1

[1] https://yoonjong-park.tistory.com/entry/Basic-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81%EA%B3%BC-View%EC%9D%98-%EC%B0%A8%EC%9D%B4

[2] https://offbyone.tistory.com/368

 

댓글