프로그래밍/Spring

[Spring] 관심사의 분리와 MVC 패턴에 대해 알아보자

byungmin 2021. 12. 29. 15:14

관심사의 분리

하나의 메서드에서 여러 기능을 하는 로직이 많아지면 유지보수 측면에서 어려움을 겪을 수 있다. 아래 코드는 main 메서드에 입력, 작업, 출력 이라는 기능(관심사)이 3개가 있는 것을 볼 수 있다. 이 코드는 단순하지만, 복잡한 기능이 하나의 메서드에 여러개 있다면 그것을 찾는 일은 어려울 것이다. 그래서 우리는 관심사를 분리해야 한다. 앞으로 배울 MVC 패턴도 관심사의 분리라는 개념에서 시작된다.

@Controller
public class UserInfo {

    @RequestMapping("/userInfo")
    public void main(HttpServletRequest request, HttpServletResponse response) throws Exception {

        // 1. 입력
        String name = request.getParameter("name");
        String tempAge = request.getParameter("age");
        String email = request.getParameter("email");

        // 2. 작업
        int age = Integer.parseInt(tempAge);

        // 3. 출력
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        PrintWriter writer = response.getWriter();
        writer.println("이름 : " + name + " 나이 : " + age + " 이메일 : " + email);
    }
}

관심사를 분리한다는 것

1. 관심사(해야할 작업)

2. 변하는 것, 자주 변하지 않는 것

3. 공통(중복) 코드 

 

OOP의 5대 설계 원칙 중 SRP (Single Responsibility Principle)

하나의 메서드에는 하나의 책임만 집중한다.

 

SRP 원칙을 적용하게 되면 각 메서드의 책임 영역이 확실하기 때문에 코드의 수정, 유지보수에서 이득을 볼 수 있다. 뿐만 아니라 책임을 적절히 분배함으로써 코드의 가독성 향상이라는 이점을 얻을 수 있다.

 

처음부터 객체지향적으로 코드 분리를 잘 할 수는 없다.

그러므로 일단 기능을 만들고 코드를 리펙토링 해보면서 관심사를 분리하는 연습을 하는 것이 중요하다.

 

공통 코드 분리 - 입력의 분리

@Controller
public class UserInfo {

    @RequestMapping(value = "/userInfo", method = RequestMethod.GET)
    public void main(String name, String age, String email, HttpServletResponse response) throws Exception {

        // 1. 입력
        // String name = request.getParameter("name");
        // String tempAge = request.getParameter("age");
        // String email = request.getParameter("email");

HttpServletRequest를 통해 매개변수를 받아왔지만, 매개변수에 요청에서 넘어온 매개변수명을 직접 입력해서 값을 받아올 수 있다.

 

공통 코드 분리 - 출력(view)의 분리

@Controller
public class UserInfo {

    @RequestMapping(value = "/userInfo", method = RequestMethod.GET)
    public void main(String name, String age, String email, HttpServletResponse response) throws Exception {

        // 1. 입력
        // String name = request.getParameter("name");
        // String tempAge = request.getParameter("age");
        // String email = request.getParameter("email");

        // 2. 작업
        int ageNum = Integer.parseInt(age);

        // 3. 출력
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        PrintWriter writer = response.getWriter();
        writer.println("이름 : " + name + " 나이 : " + ageNum + " 이메일 : " + email);
        // 입력받은 값을 바로 출력해줄 수 있다.
    }
}

기존에는 하나의 메서드에 입력과 작업, 출력 기능이 같이 있기 때문에 입력받은 값을 출력부분에 바로 넣어줄 수 있다. 하지만 작업과 출력 부분도 코드를 분리해서 메서드를 나누게 된다면 입력받은 데이터를 바로 넘겨줄 수 없게 된다.

코드의 분리로 인해서 입력받은 데이터가 있는 메서드와 출력해줄  메서드가 같지 않으므로 데이터를 넘겨줄 때 해당 데이터를 담아줄 Model 객체가 필요해진다.

 

MVC 패턴

관심사의 분리를 통해 클라이언트의 입력을 받고 (DispatcherServlet) 작업을 처리하는 부분(Controller)과 출력하는 부분(View)을 나누고 데이터를 주고 받을 수 있는 객체 (Model)를 생성하면 MVC 패턴이 완성 된다. 이 패턴을 단순화하게 구조화하면 아래 그림과 같다.

 

MVC 패턴 구조도 출처 : https://blog.woniper.net/261

실제 Spring의 MVC 패턴은 조금 더 복잡하지만 큰 그림은 위와 같다.

 

1. 브라우저의 요청

브라우저가 서버에 요청하면서 여러 메타 데이터(HttpServletRequest 객체를 통해)를 DispatherServlet에 넘겨준다. 

 

2. DispatherServlet

- 요청 데이터를 입력하고 입력 받은 데이터의 타입을 변환시켜준다.

- model 객체를 생성한다.

 

3. Controller

입력 받은 데이터를 처리하고 View에 보내줄 데이터를 Model 객체에 담아준다. 

@RequestMapping(value = "/userInfo", method = RequestMethod.GET)
public String main(String name, String age, String email, HttpServletResponse response, Model model) throws Exception {

    // 1. 유효성 검사
    if (!isVaild(name, age, email))
        return "error"; // 매개변수 값이 없다면 error 페이지 반환

    // 2. 작업
    int ageNum = Integer.parseInt(age);

    // 3. model에 데이터 저장
    model.addAttribute("name", name);
    model.addAttribute("age", ageNum);
    model.addAttribute("email", email);
    
    // 3. 출력
    return "userInfo"; // /WEB-INF/views/userInfo.jsp 페이지 반환

}

반환하는 문자열은 view 페이지의 이름이 된다. view 페이지 경로는 servler-context.xml 에서 설정가능하다.

<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<beans:property name="prefix" value="/WEB-INF/views/" />
	<beans:property name="suffix" value=".jsp" />
</beans:bean>

 

4. 컨트롤러에서 저장한 model 객체를 DispatherServlet에 다시 반환

model에 저장된 데이터는 key, value 형태로 저장된다. 또한 controller에서 return한 값은 결과를 보여줄 view 페이지 이름이 된다.

 

5. View 페이지

view 페이지에 model 객체에 담은 데이터를 넘겨준다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
이름은 : ${name} 나이는 : ${age} 이메일은 : ${email} 입니다.
</body>
</html>