모델-뷰-컨트롤러(Model–View–Controller, MVC)는 소프트웨어 공학에서 사용되는 소프트웨어 디자인 패턴이다.
이 패턴을 성공적으로 사용하면, 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다.
-wikipedia-
MVC pattern 은 프로그래밍을 할 때 전체적인 구조에 관련된 여러 디자인 패턴 중 하나이며, 도메인(비즈니스)로직과 UI로직을 분리하여 유지보수를 독립적으로 수행할 수 있게 하는 장점이 있다.
M은 Model을 가리킨다.
Model이란 프로그램이 작업하는 세계관의 요소들을 개념적으로 정의한 것이라고 볼 수 있다.
예를 들어 음식점 무인 포스기를 개발한다고 가정해보자. 무인포스기가 정상적으로 목표하는 작업을 수행하기 위해서는 우선 메뉴가 있어야하고, 메뉴를 담을 수 있는 장바구니, 해당 메뉴의 수량, 결제수단, 할인정책 등등이 필요할 것이다.
이처럼 프로그램이 목표하는 작업을 원활하게 수행하기 위해 필요한 물리적 개체, 규칙, 작업등의 요소들을 구분되는 역할로써 정의해놓은게 Model이 된다. Model은 DTO와 DAO로 분류할 수 있다.
두 개념에 대해서는 나중에 다른 세션에서 정리할 예정이므로 간단히 언급만하고 넘어가겠다.
결과적으로 Model을 잘 설계하는 것은, 해당 도메인 세계를 얼마만큼 이해하고 있는지와도 밀접한 연관이 있다. 꼭 물리적인 요소뿐만아니라 추상적인 요소 또한 해당 작업을 수행하는데 특정 책임과 역할로서 구분될 수 있다면 최대한 구체적이고 작은 entitiy를 유지하면서 Model을 설계하는 것이 중요하다.
V는 View를 가리키고 사용자가 보는 화면에 입출력 과정 및 결과를 보여주기 위한 역할을 한다. 입출력의 순서나 데이터 양식은 컨트롤러에 종속되어 결정되고, 도메인 모델의 상태를 변환하거나, 받아서 렌더링하는 역할을 한다.
view를 구현할 때 주의할 점은 도메인 로직의 어떤 것도 알고 있으면 안된다는 것이다. 절대적으로 객체를 전달받아 상태를 바로 출력하는 역할만을 담당해야 한다. 그렇기 때문에 view에서는 도메인 객체의 상태를 따로 저장하고 관리하는 클래스 변수 혹은 인스턴스 변수가 있을 필요가 없다.
C는 Controller를 가리킨다. controller는 model과 view를 연결 시켜주는 다리 역할을 함과 동시에 도메인 객체들의 조합을 통해 프로그램의 작동 순서나 방식을 제어한다. controller는 view와 model이 각각 어떤 역할과 책임이 있는 지 알고 있어야 한다.
웹 프로그래밍에서는 Controller에서 service layer를 분리하여 domain 로직이 수행되는 곳과 view의 요청을 매핑하는 곳을 독립적으로 관리할 수 있다.
View
뷰는 사용자의 동작과 연관되어 있는 부분이 많고, 비교적 자주 변경되는 부분이라 웹 애플리케이션 개발에서 많은 시간이 소요되는 영역이다. 때문에 뷰를 TDD로 개발하는 것은 효율이 낮을 뿐만 아니라 쉽지도 않다. 그리고 뷰에는 다양한 요소들이 혼재되어 있다. 그럼에도 보통은 이를 분리해서 생각하지 않고 뷰라는 한 가지 개념으로만 접근하기 때문에 더욱 어렵게 느껴지기 쉽다.
Controller
결론부터 이야기하자면, 컨트롤러에 대한 TDD 작성은 비교적 수월하다. MVC 모델에서 컨트롤러를 테스트하는 가장 간단한 방법을 소개하면 뷰로부터 넘어오는 요청 (Request)를 가상
으로 만들어주고, 그 결과에 해당하는 응답이 예상과 일치하는지 판단하면 된다.
모델 테스트보다는 귀찮고 복잡하지만 controller를 테스트하는 것도 충분히 가능하고, 프레임워크를 쓰면 manual로 하는 것보다 훨씬 수월하게 할 수 있다고 한다.
Model
위키에 나온 controller의 정의만 보더라도 위 물음에는 대답이 명확히 나온다.
MVC의 뷰는 여러 개의 컨트롤러(Controller)를 가지고 있다. 사용자는 컨트롤러를 사용하여 모델의 상태를 바꾼다. 컨트롤러는 모델의 mutator 함수를 호출하여 상태를 바꾼다. 이때 모델의 상태가 바뀌면 모델은 등록된 뷰에 자신의 상태가 바뀌었다는 것을 알리고 뷰는 거기에 맞게 사용자에게 모델의 상태를 보여 준다. - wikipedia-
따라서 controller는 하나일 필요가 없다. 프로그램이 실행될 때 로직이 병렬적으로 분기된다면 controller를 여러개 둬도 상관없다. 오히려 그게 더 깔끔한 설계가 될 수 도 있다.
최근 치킨집 포스기 구현 스터디를 통해 나도 새롭게 깨달은 부분인데 controller는 하나만있어야 된다는 나의 고정관념을 깰 수 있었던 좋은 귀감이 되었다. 적용 예시는 아래 코드로 대신한다.
처음 메인 기능을 선택하는 입출력 화면이 나오고, 유저의 입력에 따라서 각 controller가 실행된다.
public void run() {
PosNumber posNumber;
do {
posNumber = getPosWithValidation();
PosController controller = posNumber.getController();
controller.controlAction(tables, menus);
// posNumber에 따라서 각각의 controller가 실행됨.
} while (posNumber.isNotExit());
}
각 컨트롤러는 Interface를 구현하는 방식으로 만들면, 유저 입력에 따라 if문으로 분기하여 controller를 실행하는 번거로움을 제거할 수 있고, 다형성을 이용하여 유지보수 및 확장할 때도 이점을 확보할 수 있다.
// PosController Interface
public interface PosController {
void controlAction(Tables tables, Menus menus);
}
사용자가 보는 페이지, 데이터처리, 그리고 이 2가지를 중간에서 제어하는 컨트롤, 이 3가지로 구성되는 하나의 애플리케이션을 만들면 각각 맡은바에만 집중을 할 수 있게 된다. 도메인을 작은 역할 단위로 분리하여 설계하는 것도 일종의 분업이라고 할 수 있지만 전체적인 구조에서도 MVC 패턴은 분업을 만들어 낼 수 있다.
서로 분리되어 각자의 역할에 집중할 수 있게끔하여 개발을 하고 그렇게 애플리케이션을 만든다면, 유지보수성, 애플리케이션의 확장성, 그리고 유연성이 증가하고, 중복코딩이라는 문제점 또한 사라지게 된다.
그렇지 않다. MVC 패턴도 수많은 디자인 패턴 중 단순하고 명료해서 기본적으로 많이 사용되는 패턴일 뿐, 한계점도 당연히 가지고 있다.
한 Model은 다수의 View들을 가질 수 있고 반대로 Controller를 통해서 한 View에 연결되는 Model도 여러 개가 될 수 있다.
이렇게 되면 결과적으로 View와 Model이 서로 의존성을 띄게 됩니다. 물론 설계를 잘 한다면 Model간의 의존성 뿐만아니라 View와 Model 사이에 생기는 의존성도 줄일 수 있지만, 프로그램 특성상 필연적으로 화면에 복잡한 화면과 데이터의 구성 필요한 구성이라면, Controller에 다수의 Model과 View가 복잡하게 연결되어 있는 상황이 생길 수 있다.
이렇게 MVC 규모 자체가 너무 복잡하고 비대해져서, 새 기능을 추가할때마다 의존성을 일일이 해결해야해서 메소드 분석이나 테스트도 어렵게 된다. 이런 형태의 MVC를 Massive ViewController라고 부르는데, 이는 MVC의 한계를 표현한 용어이기도 한다.
View와 Model을 중재하는 Controller의 비중은 별로 크지 않을 것으로 생각할 수 있지만, 복잡한 화면을 구현하게 되면 Massive ViewController가 될 수 밖에 없다.
Controller는 View와 라이프 사이클이 강하게 연결되어 있어서 분리하기도 어렵고, 유지보수와 테스트가 모두 힘들어진다.
이러한 한계점을 갖고 있기 때문에 MVC로 부터 파생된 다른 패턴들을 공부해보는 것도 나중엔 도움이 될 것이라고 생각한다.
<참고자료>
위키피디아, MVC
TDD 실천법과 도구 - 한빛 미디어
https://m.blog.naver.com/jhc9639/220967034588