[Spring]스프링 기초 - MVC모델

Inung_92·2023년 1월 30일
1

Spring

목록 보기
1/14
post-thumbnail

MVC 모델이란?

📖소프트웨어 공학에서 사용되는 디자인 패턴 중 하나로 어플리케이션을 3개의 영역으로 분할하고 각 구성 요소에게 고유한 역할을 부여하는 개발 방식

⚡️MVC 모델이 필요한 이유

MVC패턴은 대규모(엔터프라이즈급) 프로그램에서 기존 소규모 프로그래밍의 문제점을 보완하기 위하여 사용한다. 그렇다면 어떤 점이 문제가 있는지 알아보자. 만약 하나의 웹 어플리케이션을 개발할 때 동일한 파일 내에 모든 로직과 데이터를 포함하여 작성하고, 구동한다면 어떻겠는가?

프로그램 개발 시 단일 파일에서 개발하면 문제점

핵심은 각 데이터와 로직이 필요한 기능별로 구분이 되어지지 않으니 프로그램을 실행하는데 불필요한 step이 많아지며, 유지보수를 해야하는 상황이 올 경우 해당 부분을 전체 코드내에서 찾다보니 시간도 많이 소요될 것이다. 또한, 디자인 영역을 수정하는 동안에는 로직에 대한 부분을 동시 수정하지 못하는 단점 또한 발생한다.
예를 들어 웹 기반으로 구동되던 프로그램을 사용자의 요구사항에 맞춰 Java의 독립실행형으로 변경해야한다면 모든 코드를 다시 분석하고 수정해야하는 불편한 상황이 벌어지는 것이다.

그렇다면 이러한 문제점을 해결하기 위해서는 어떤 부분을 개선해야하는지 알아보자.

문제점 해결 방향

위에서 단일 파일 내에 프로그램을 작성하는 것에 대한 문제를 제시했다. 한번 문제를 추출해보자.

  • 기능 또는 유형별 코드를 분석하기 제한
  • 동시 수정 제한(디자인 영역 수정 중에는 로직 수정이 불가능)
  • 프로그램 성격 변경 시 코드를 분석하는데 시간이 오래걸리며 로직 분할이 제한
  • 프로그램을 실행 시 불필요한 조건 분석 등의 Step(단계)이 많아져 성능이 저하

더 많은 문제점이 있을 수 있지만 대표적인 문제들에 대해서만 추출해보았다. 그렇다면 해결하기 위해서는 어떻게 해야할까? 완벽한 해결은 이 세상에 존재할 수 없지만 그래도 한번 알아보자.
우선 문제점들을 보다보니 하나의 문제점에서 파생된 것이라고도 볼 수 있는데 그것은 바로 '분리'가 불가능하다는 것이다. 즉, 기능 또는 유형별로 컴포넌트를 분리하여 작성하면 다음과 같은 부분들이 해결될 것이다.

  • 기능 및 유형에 해당하는 컴포넌트의 코드만 분석하기 용이해짐
  • 하나의 컴포넌트 영역을 수정하는 동안 다른 컴포넌트 수정으로 동시수정이 가능해짐
  • 프로그램의 성격이 변경될 경우 변경되는 부분만 코드 분석과 수정을 겪으면됨
  • 프로그램 실행 시 유형에 맞는 부분만 실행하여 불필요한 Step최소화로 성능 향상

이렇게 문제점들이 해결되면 각 기능별 컴포넌트들 간의 결합도(Coupling)가 높아지는 것을 낮출 수 있다. 결합도가 높다는 것은 어떤 영역을 수정 및 변경할 경우 추가로 작업을 해야하는 부분들이 많아진다고 생각하면 좋을 듯하다. 이 말은 결합도가 낮으면 어떤 영역을 수정 및 변경해야하는 경우 해당 부분만을 신경쓰면 된다는 뜻이기도 하다.

그렇다면 MVC 모델이 왜 필요한지 알아보았으니 그럼 MVC모델이 어떻게 구성되어 있는지 그리고 각 컴포넌트들은 어떤 역할을 하는지 등에 대하여 알아보자.


MVC 모델 구조

📖MVC(Model View Controller)는 이름에서 알 수 있듯이 Model, View, Controller라는 세 개의 컴포넌트로 구성

MVC 역할

역할을 설명하기 전에 잠시 MVC 모델 1과 2에 대하여 알아보고 넘어가자.

  • 모델1 : MVC를 JSP만으로 디자인 및 컨트롤러까지 담당하는 MVC패턴 방식
  • 모델2 : MVC를 JavaEE로 구현한 MVC패턴 방식으로 M(.java), V(view), C(controller)로 컴포넌트를 구분

모델 1과 모델 2에 대한 혼동을 줄이기 위하여 설명한 것이니 참고용으로만 보고 MVC 역할에 대해서 알아보자.

  • M(Model) : 로직을 통하여 Data 및 정보 가공을 처리하는 컴포넌트이며, 재사용 가능한 중립적 코드.java(POJO) 사용
  • V(View) : 클라이언트에게 시각적으로 출력할 디자인, 즉 User Interface를 관리한다. 또한, 웹 서버에서 실행될 수 있으며 디자인 표현이 가능한 jsp를 사용
  • C(Controller) : Model와 View사이에서 브릿지(Bridge)구분자 역할을 한다. 또한, 웹 서버에서 실행 될 수 있으며 클라이언트의 요청과 응답을 처리할 수 있는 Servlet을 사용

이렇게 역할을 구분하면 기능별 코드가 분리되어 차후 유지보수 및 사업의 요구사항 변경 시 최소한으로 코드를 수정하기 용이하다.

역할에 대한 예시

MVC 역할에 대해서 현실세계의 프랜차이즈 햄버거 가게를 예시로하여 알아보자. 메뉴판은 view, 주문을 받는 직원은 controller, 음식을 만드는 직원 model로 비유하겠다.

⚡️View

view의 기능을 담당하는 개체를 햄버거 가게에서 찾아보면 최근에는 키오스크를 통한 메뉴판 등을 예로 들 수 있을 것이다. 햄버거 메뉴 및 가격 등이 출력되며, 메뉴를 선택하여 결제까지 가능한 것이다. view는 데이터에 직접 접근하는 것이 아닌 가공된 데이터를 선택, 열람 등의 활동을 할 수 있는 컴포넌트이다.

⚡️Controller

controller는 햄버거 가게에서 주문을 받는 데스크 직원이라고 생각하면 될 것이다. view에서 클라이언트가 선택한 메뉴에 대한 주문을 받고, 어떤 상품인지 알려주는 역할을 하는 것이다. 즉 view와 model을 연결하는 역할을 하는 것이다.

⚡️Model

Model은 햄버거를 직접 만드는 직원이다. 데스크 직원으로부터 전달받은 메뉴를 확인하고 냉장고(DB)에서 재료(데이터)를 꺼내고, 준비된 데이터로 조리를 하는 것이다. 이렇게 조리가 완료된 햄버거를 controller에게 반환한다.

이렇게 3가지 역할을 햄버거 가게로 비유해보았다. 아래의 그림을 통해 다시 한번 흐름을 이해해보자.

이렇게해서 MVC의 구조 및 역할까지 알아보았다. 그렇다면 MVC의 한계는 어떤 것이 있는지 알아보자.

MVC의 한계

앞서 설명한 것처럼 MVC는 대규모 프로그램을 개발하는 경우 사용된다. 이 때 다수의 view와 model이 controller를 통해 연결되기 때문에 controller가 불필요하게 비대해지는 경우가 발생한다. 이러한 현상을 'Massive-View-Controller'라고 한다. 다음 그림으로 이해해보자.

위와 같이 대규모 프로그램은 요청이 많기 때문에 해당 요청을 1:1로 처리하는 controller를 만들면 controller의 개수는 엄청나게 많아질 것이다. 이러한 부분을 해소하기 위해서 중앙에서 controller들을 통제하는 controller를 두어 하위 controller들 중 요청 유형에 맞는 업무를 분담해주는 방식으로 문제점을 해결할 수도 있다.


MVC 예제

이제 MVC에 대하여 어느정도 알아보았으니 간단한 예제를 통해서 단일 파일 내에있는 코드가 MVC의 역할 별로 컴포넌트를 나눌때 어떤 효과가 있는지 알아보자. 스프링은 아직 배우지 않았으니 현재까지 배운 문법으로 예제를 진행하겠다.😞

⚡️ 예제 시나리오

  • 혈액형 정보를 출력하는 프로그램을 작성
  • 최초에는 1개의 JSP파일 만으로 작성
  • 단계별로 Model, View, Controller 영역으로 컴포넌트를 구분
  • 2개의 View를 구성하고 Controller에게 요청을 보내 Model을 통해 응답전송

그림까지 넣으니 시나리오는 거창하다. 하지만 코드는 간단할 것이다. 왜냐면 혈액형 정보는 단 한마디로 작성할 것이기 때문이다. 그럼 시작해보자.

🖥️ 단일 파일 내 코드 작성(.jsp)

<%@ page contentType="text/html;charset=utf8"%> //지시부 선언

<% //스크립틀릿 선언
	request.setCharacterEncoding("utf-8");
    // 요청 처리 및 결과 반환
    String bloodType = request.getParameter("blood");
    // 조건별 처리결과를 담을 변수
    String result = null;
    
    switch(bloodType){
    	case "A": result = "성격이 꼼꼼함"; break;
    	case "B": result = "자신의 주관이 뚜렷함"; break;
    	case "O": result = "활발함"; break;
    	case "AB": result = "생각이 깊음"; break;
    } //결과는 어디까지나 예제를 위해 개인적인 생각을 나열한 것입니다...😞
    
    // 요청 유형의 맞게 처리된 결과 출력
    out.print(result);
%>

<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>Insert title here</title>
  </head>
  
  <body>
      <div>
      	//Post방식 전송을 위해 name 속성부여
      	<form name="form1">
        	//서버에서 value 획득을 위한 name 속성부여
      		<select name="blood">
            	<option value="A">A</option>
				<option value="B">B</option>
				<option value="O">O</option>
				<option value="AB">AB</option>
        	</select>
            //버튼 이벤트 연결
            <button type="button" onClick="send()">분석</button>
        </form>
      </div>
  </body>
  <script type="text/javascript">
  	function send(){
    	//현재 JSP URL 주소로 전송
		form1.action="현재 jsp URL";
        form1.method="POST";
        form1.submit();
    }
  </script>
</html>

여기까지 단 하나의 .jsp 파일로 모든 역할을 하고 있는 프로그램의 코드이다. 현재 상태는 프로그램에 결과가 가공되지 않은 상태로 출력된다. 이러한 코드를 그래도 조금은 신경써서 분리를 해보자. 어떻게? 바로 요청하는 페이지는 순수 디자인만 가지며, 요청을 받고 응답하는 페이지는 Controller와 Model이 공존하는 것이다.

🖥️ View만 분리

<%@ page contentType="text/html;charset=utf8"%> //지시부 선언
<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>Insert title here</title>
  </head>
  
  <body>
      <div>
      	//Post방식 전송을 위해 name 속성부여
      	<form name="form1">
        	//서버에서 value 획득을 위한 name 속성부여
      		<select name="blood">
            	<option value="A">A</option>
				<option value="B">B</option>
				<option value="O">O</option>
				<option value="AB">AB</option>
        	</select>
            //버튼 이벤트 연결
            <button type="button" onClick="send()">분석</button>
        </form>
      </div>
  </body>
  <script type="text/javascript">
  	function send(){
    	//현재 JSP URL 주소로 전송
		form1.action="/blood/result.jsp"; //분리된 페이지 URL
        form1.method="POST";
        form1.submit();
    }
  </script>
</html>

다음은 요청을 처리하고 결과를 보여주는 .jsp 코드이다.

🖥️ Model + Controller 코드(.jsp)

// import 생략...
<%@ page contentType="text/html;charset=utf8"%> //지시부 선언

<% //스크립트릿 선언
	request.setCharacterEncoding("utf-8");
    // 요청 처리 및 결과 반환
    String bloodType = request.getParameter("blood");
    // 조건별 처리결과를 담을 변수
    String result = null;
    
    switch(bloodType){
    	case "A": result = "성격이 꼼꼼함"; break;
    	case "B": result = "자신의 주관이 뚜렷함"; break;
    	case "O": result = "활발함"; break;
    	case "AB": result = "생각이 깊음"; break;
    } //결과는 어디까지나 예제를 위해 개인적인 생각을 나열한 것입니다...😞
%>

<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>Insert title here</title>
  </head>
  
  <body>
      <div>
		<h1>"선택하신 혈액형에 대한 평가는?"</h1>
        <%=result %> // 결과에 대한 표현식 사용
      </div>
  </body>
</html>

이렇게하면 그나마 조금 분리는 되었다. 근데 문제는 이 상태에서 만약 고객이 해당 프로그램을 Swing Gui기반으로 바꾸고 싶다고 요구한다면? 디자인만 바꾸면 되는가? 아니다. 모든 코드를 다시 분리하고 따로 저장해두고 디자인 작업은 디자인 작업대로 해야한다. 이러한 이유 때문에 코드를 분리하는 것이 유지보수가 용이하다는 것이다. 그렇다면 여기서 중립적으로 사용할 수 있는 코드가 어느 부분인지 보자.
Model을 구성할때는 자바의 순수한 객체(POJO)를 사용한다고 했다. 그렇다면 분리시켜서 어느곳에 사용해도 재사용이 가능한 코드를 위 코드에서 추출해보겠다.

String result = null;

switch(bloodType){
  case "A": result = "성격이 꼼꼼함"; break;
  case "B": result = "자신의 주관이 뚜렷함"; break;
  case "O": result = "활발함"; break;
  case "AB": result = "생각이 깊음"; break;
}

바로 이 부분이 아닌가 싶다. 요청객체를 처리하는 것은 서블릿 또는 JSP의 내장객체로 처리해야한다. 결국 웹 서버가 가동되지 않으면 요청에 대한 로직을 처리가 불가능하다. Swing의 경우 무조건은 아니지만 대부분 독립실행형으로 사용되기 때문에 웹 서버와의 요청 및 응답은 없다고 판단하면 편할 것이다. 자 그럼 위 코드만 Model로 분리시켜서 .java 코드를 작성해보자.

🖥️ Model 코드 작성(.java)

package mvc.model;

//해당 클래스는 웹 또는 독립실행형 모두 사용 가능한 중립적인 코드
//재사용성을 위해 기존 jsp에서 해당 코드를 분리
public class BloodAdvisor {
	
	public String getAdvice(String bloodType){
    	//result를 value로 변수명만 수정하겠다.
    	String value = null;
        //파라미터에 따라 해당하는 데이터 대입
        switch(bloodType){
          case "A": value = "성격이 꼼꼼함"; break;
          case "B": value = "자신의 주관이 뚜렷함"; break;
          case "O": value = "활발함"; break;
          case "AB": value = "생각이 깊음"; break;
		}
        //결과 반환
    	return value;
    }
}

이렇게 작성되면 위에서 작성한 result.jsp의 코드가 한번 더 분리된 것이다. 아래 코드를 보자.

// import 생략...
<%@ page contentType="text/html;charset=utf8"%> //지시부 선언
<%! //선언부 선언
  	//Model 객체를 선언하여 요청 처리에 대한 데이터 가공
	BloodAdvisor advisor = new BloodAdvisor();
%>

<%
  request.setCharacterEncoding("utf-8");
  // 요청 처리 및 결과 반환
  String bloodType = request.getParameter("blood");  	
%>
<%!%>
<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>Insert title here</title>
  </head>
  
  <body>
      <div>
		<h1>"선택하신 혈액형에 대한 평가는?"</h1>
		// 함수를 표현식 내부에 사용
        <%=advisor.getAdvice(bloodType); %>
      </div>
  </body>
</html>

위 코드를 보면 Model 객체로 요청 객체의 파라미터를 전달하여 처리에 필요한 데이터를 가공하는 절차를 거쳤다. 이렇게해서 View와 Model이 어느정도 분리된 것이다.
하지만 아직 요청을 받고 요청에 대한 파라미터를 넘겨주는 절차를 View에서 실행하고 있다. 이 부분을 또 한번 분리해보자. 그럼 웹 서버에서 요청을 받을 수 있고, 응답을 할 수 있는 객체는 무엇인가? 바로 서블릿이다. 서블릿을 통해 Controller를 구성해보자.

🖥️ Controller 코드(Servlet)

public class BloodController extends HttpServlet{
	//model 보유
    BloodAdvisor advisor = new BloodAdvisor();
    
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
        //요청 객체 파라미터 획득
		String bloodType = request.getParameter("blood");  
        
        String result = advisor.getAdvice(bloodType);
        
        // 응답 결과를 View에서 사용하기 위한 방법은 2가지이다.
        // 1. 세션에 저장 : 사용량이 많아지면 서버에 부담이 많이감.
        // 2. forwording으로 View jsp로 요청 및 응답객체 전달
        
        // 2번 방법을 사용해보자. 
        request.setAttribute("result", result);
        RequestDispatcher dis = request.getgetRequestDispatcher("/blood/result.jsp");
        dis.forword(request, response);
    }
}

이렇게 해서 Controller까지 분리를 완료시켰다. 그럼 한번 result.jsp가 어떻게 바뀌었는지 보자.

<%@ page contentType="text/html;charset=utf8"%> //지시부 선언
<%
	//현재 request는 forwoding으로부터 유지되어온 객체
	String result = (String)request.getAttribute("result");
%>
<%!%>
<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>Insert title here</title>
  </head>
  
  <body>
      <div>
		<h1>"선택하신 혈액형에 대한 평가는?"</h1>
		// 함수를 표현식 내부에 사용
        <%=result %>
      </div>
  </body>
</html>

View에는 요청에 대한 파라미터를 분석한다던지 무언가를 전송하는 로직은 없다. 가공된 응답 결과만을 클라이언트에게 출력하여 시각화로 보여줄 뿐이다. 이렇게만해도 MVC를 모두 분리한 코드가 완료되었다.
이제 디자인에 대한 유지보수가 필요한 경우에는 .jsp만 수정하면 된다. 동시에 Model(.java)도 수정이 가능하기 때문에 동시다발적인 유지보수가 확보된다. 이 뿐만 아니라 우리가 앞서 이야기했던 문제점들의 대부분이 '분리'를 통해 꽤 많이 해소되었음을 알 수 있다.


마무리

이렇게해서 이번 게시글에서는 MVC 모델에 대해서 알아보았다. 항상 공부를 하면서 많이 들었던 이론이지만 실제로 사용해 본 적은 없었기에 오늘 예제를 통해 경험을 하다보니 분리되면 당연히 신경쓰고 준비해야하는 제반사항은 많아지지만 나중을 생각하면 왜 선배 개발자들은 이런 이론을 적용시켰는지 조금은 이해가 된 것 같다.

느낀점

  • 단순히 코드를 깔끔하게 정리하는 개념이 아닌 기능 또는 유형별 분리를 통해 재사용성을 높이고 유지보수 시간을 단축하는 것이 가능하다는 것을 알게됨.
  • 엔터프라이즈급 대규모 프로그램은 요청 유형을 분석하고 해당 유형에 대응되는 처리를 함으로서 적절한 응답결과를 반환하는 것이 중요하다는 것을 알게됨. 불필요한 로직을 수행하면 처리시간 지연 및 성능의 저하가 생김.
  • 스프링을 사용하기 전 원리에 대해 조금이라도 더 알고 넘어가는 느낌이어서 다행이라고 생각함. 그저 편하게 사용하면 어딘가 모를 찝찝함이 남아있음.
  • 오늘 배운 이론을 바탕으로 스프링에 대한 공부를 시작할 때 MVC의 이점은 스프링에서 어떻게 나타나고 스프링으로 어떻게 더 효과적으로 적용할 수 있는지에 대해서 생각하며 스프링 공부를 하려한다.

참고
https://junhyunny.github.io/information/design-pattern/mvc-pattern/

profile
서핑하는 개발자🏄🏽

0개의 댓글