뉴비를 위한 MV* 패턴 #1/3

개요

프로그래밍에는 다양한 분야가 있죠. 일반적으로 애플리케이션(Application)이라 불리는 응용 프로그램을 작성하는 것은 직업 개발자에게 가장 흔한 경우입니다. 웹서비스, 게임, 모바일용 앱 등 애플리케이션의 큰 특징은 사용자와 인터렉션하는 ‘뷰(view)’를 포함하고 있다는 점입니다.

보통 애플리케이션은 복잡한 상호 작용과 많은 뷰를 갖고 있을 뿐만 아니라, 기능의 수정과 추가도 상당한 빈도로 일어나기 때문에 복잡성을 격리하여 관리할 수 없는 경우, 유지보수는 커녕 개발 완료조차 성공하기 어렵습니다.

이렇듯 감당하기 어려운 변화가능성과 복잡성을 적절하게 나눠서 격리한 뒤, 통제 가능한 범위로 개발하는 방법론은 여럿 나와 있습니다만 가장 유명한 것이 바로 MV*(모델뷰)패턴이라 불리는 일종의 아키텍쳐 패턴입니다.

많은 웹문서와 책들에서 MV*패턴에 대한 설명을 읽어보면 뉴비 입장에서 이해하기 너무 어렵게 쓰여진 경우가 대부분이었습니다. 하지만 애플리케이션 개발에는 핵심적인 내용이다 보니 보다 쉽게 이해할 수 있는 글이 필요하다고 느껴 정리하게 되었습니다(저 자신도 잘 모르는 부분을 다시 한 번 정리하면서 검토해볼 겸사겸사 ^^)

총 4회차로 구성된 이 글은

  1. 모델뷰패턴의 기본
  2. MVC패턴
  3. MVVM패턴

으로 구성됩니다. 회차가 진행됨에 따라 앞의 패턴과 비교하는 내용도 많아지므로 차근차근 보실 것을 권합니다. 이 글에서는 개별 구성 요소인 M의 Model과 V의 View를 살펴보고 앞으로 다루게 될 세 가지 패턴에 대한 기초적인 설명을 하려 합니다.

모델(Model)

모델은 데이터입니다.
현실 세계에서는 뭐가 되었든 굉장히 많은 정보를 가진 실체가 있기 마련입니다. 예를 들어 사람을 정보로 표현하려면 수 만 개의 필드가 있어도 여전히 부족할 것입니다.
이 중에 ‘이 상황에서 기억해야만 하는 것’을 추려낸 것이 모델입니다.

프라모델을 예로 들어볼까요? 현실 세계의 자동차에서 일부 필요한 특성만 뽑아 만든 게 ‘프라모델 자동차’입니다. 이 때 주어진 상황은

  1. 전시나 감상을 목적으로 한다.
  2. 프라스틱으로 제작한다.
  3. 엔진룸 등의 내부에 보이지 않는 부분은 재현하지 않는다.

등으로 요약할 수 있습니다. 이 상황에서 어디까지를 데이터에 포함될 지를 알 수 있습니다.
즉 이를 베이스로 “기억해야만 하는 것”을 추리면

  1. 축척에 맞는 각 외관부의 수치
  2. 도색에 필요한 각 부분의 색상 정보

등이 될 수 있겠죠. 이를 바탕으로 만들어진 것이 바로 현실의 자동차에서 일부 필요한 내용으로만 재구성한 자동차의 모델이 됩니다.

이처럼 모델이란 추상화의 한 가지 기법으로, 복잡한 실체를 그대로 사용하기엔 무리가 있어 필요한 만큼의 정보만 재구성한 추상 객체라 할 수 있습니다.

모델을 만드는 과정을 모델링이라 하는데, 모델링을 하기 힘들다면 ‘정말 기억해야만 하는 것은 무엇일까’라는 잣대로 판단하시면 많은 도움이 됩니다.

일반적인 모델론에서 약간 벗어나 MV*패턴에서 모델은 애플리케이션 그 자체를 의미합니다.
애플리케이션에서 가장 핵심이 되는 것은 바로 이 데이터를 담당하는 모델로, 모델을 중심으로 하는 관점에서는 나머지 구성요소는 그저 모델을 예쁘게 포장하는 장치로 이해됩니다. 따라서 견고한 모델을 작성했는가가 바로 애플리케이션의 핵심적인 안정성과 기능을 좌우하게 됩니다. 하지만 견고한 모델을 작성하는 것은 그리 쉬운 문제는 아닙니다. 모델은 중복된 상을 가리키기 쉽고 역으로 하나의 상을 여러 개의 모델에서 동시에 다루기도 쉽습니다. 고전적인 모델링의 기본은 관계형데이터베이스의 모델링이나 객체관계모델링입니다. 모델링을 잘하는 방법은 여러가지 있지만 실질적으로는 수 많은 연습이 필요한 분야입니다.

뷰(View)

View는 크게 두 가지를 담당하는데,

  1. 하나는 내부의 모델을 외부에 이쁘게 보여주는 역할이고
  2. 또 한 가지는 외부의 입력을 받아들여 내부에 전파하는 역할을 합니다.

손쉽게 웹사이트의 게시판을 생각해보면 데이터베이스에 기록된 여러 포스팅을 이쁘게 보여주면서, 동시에 댓글을 달거나 조회수를 높이는 등의 외부 입력을 서버나 앱 내부에 알려주는 식입니다.
뷰의 역할과 별도로 일반적으로 뷰가 갖는 특성은 크게 두 가지가 있습니다.

  1. UX/UI – 사용자에게 보여주고 또한 사용자에게 입력을 받는 행위는 인간공학의 영역입니다. 따라서 사람을 연구하고 사람의 인지적이고 관습적인 행위에 잘 부합하도록 다양한 디자인 요소를 도입하게 됩니다. 이 과정에서 뷰의 기술적인 난이도가 크게 올라가고 애니메이션 등을 비롯한 cpu부하가 크게 걸리게 되어 다양한 기법이 총동원됩니다. 뷰는 매일 요구사항이 높아지고 화려해지기 때문에 고난이도의 기술력을 동원해야 트랜드에 뒤쳐지지 않게 됩니다. 일반적으로 모든 개발자가 이러한 요구사항을 처리할 수 있는 능력은 없으므로 프레임웍이나 라이브러리에 의존하는 경우가 대부분입니다. 반대로 그러한 일반화된 라이브러리보다 더욱 특화된 기능을 제공할 수 있으면 그것 만으로도 앱의 차별성이 이루어지곤 합니다.
  2. 막대한 양 – 모델이 데이터베이스의 테이블 한 개라 할지라도 뷰는 이를 다양한 형태로 가공하여 보여주므로 몇 배나 많은 산출물이 만들어지기 마련입니다. 예를 들어 게시판을 저장하는 테이블하나만 해도 그걸 바탕으로 리스트, 뷰, 수정과 같은 뷰는 물론 수치통계, 그래프, 대쉬보드 등의 뷰도 만들게 됩니다. 따라서 뷰의 일반적인 특성은 작업량이 다른 어떤 파트와 비교할 수 없을 정도로 막대하다고 할 수 있습니다.

연결(*)

어떠한 앱이든 모델과 뷰는 필수적입니다. 하지만 뷰와 모델 사이를 어떻게 연결하고 관리할 것인가가 바로 핵심적인 관심사입니다. 이 연결 부분과 연결 방식에 따라 업계에서는 다음과 같은 유형을 만들어냈습니다.

MVC(모델뷰컨트롤러)

Controller라는 객체를 중간 조정자로 두는 방식입니다. 모델과 뷰의 조정이라는 게 쉽지 않은데 컨트롤러라는 만능 객체에게 맡기는 셈이라 금새 컨트롤러가 굉장히 복잡해지는 모순에 빠지게 되는 것이 단점입니다. 이를 보완하는 다양한 기법이 있습니다만 이로 인해 난이도가 급격하게 올라가게 됩니다.

MVP(모델뷰프리젠터)

Presenter는 뷰에 대한 완전한 통제를 갖게 됩니다. 반대로 말하면 뷰는 자율성을 완전히 잃어버리고 프리젠터가 하자는 대로 하게 됩니다. 이를 통해 얻을 수 있는 장점이라면 뷰가 모델은 물론 프리젠터조차 모르게 된다는 점입니다. 하지만 이상과는 달리 모델의 변화는 프리젠터의 변화를 일으키고 그러한 프리젠터와 상호작용하기 위한 뷰에 차례로 여파가 일어나게 됩니다.
특히 뷰와 프리젠터의 관계를 명시적으로 맺지 않기 위한 장치는 코드 상으로 보면 뷰를 통제하는데 필요한 모든 게터 세터를 뷰에서 제공하는 형식이라 반복적인 코드가 대량으로 발생하게 됩니다. 이를 자동으로 생성해주는 비쥬얼스튜디오 등의 IDE에서 착안된 방식으로 툴이 게터 세터를 자동으로 다 만들어준다면 그리 나쁜 것만도 아닙니다.
대신 이 경우에도 뷰에서 제공되는 필드는 정해져 있으므로 실제 모델과 맞지 않는 부분은 프리젠터가 전부 조정해서 뷰필드에 맞게 보내줘야 합니다.
앱 개발이 진행될 수록 프레젠터가 비대해지고 커스텀화된 뷰가 많이 등장하는데 모델과의 관계를 명확하게 인식하기도 힘들기 때문에 오히려 난이도가 높아지곤 합니다.

MVVM(모델뷰뷰모델)

ViewModel 이라는 다소 애매한 이름의 중간 조정자가 등장하는 패턴입니다.
MVC, MVP패턴에서 몇 가지 문제를 인식하게 되었는데 바로 뷰를 초기화하고 소유하는게 컨트롤러나 프리젠터라는 점입니다. 이 관계에서 하나의 컨트롤러나 프리젠터는 여러 개의 뷰를 소유할 수 있습니다.
이 관점은 초창기 모델과 뷰를 바라보던 관점을 계승합니다. 즉 하나의 모델이 여러 개의 뷰로 표현될 수 있기 때문에 모델을 감싸는 중간 역할자가 다양한 뷰를 골라서 대응해야 한다는 관점입니다. 하지만 애플리케이션이 고도화되면서 그렇지 않다는 점을 발견하게 되었습니다.

하나의 뷰가 여러 개의 모델로부터 정보를 얻어야 그릴 수 있다는 점을 알게 되고 그 결과 프리젠터든 컨트롤러든 이 부분의 대응을 위해 n개의 모델을 가져와 가공한 뒤 n개의 뷰에게 대응시켜야하는 n:n 문제에 봉착하여 코드가 굉장히 복잡하고 비대해져 버립니다.

이런 뷰 한 개에 조정자 하나를 대응시키는 방식을 좀 더 과감히 벗어나 뷰가 중간조정자를 여럿 소유할 수 있는 제어역전을 시도하게 됩니다. 따라서

  1. 뷰가 여러 개의 뷰모델을 알고 있고
  2. MVP의 뷰 게터는 제거되고(뷰모델은 직접 뷰를 컨트롤하지 않음)
  3. 대신 뷰입력이 여러 뷰모델 중 하나에게 직전 전파되며
  4. 뷰모델은 모델에 대한 처리 후 다시 뷰세터에게 통지하면
  5. 뷰가 스스로 상태를 고치는 식입니다.

이 형태에서 뷰는 보다 자율적이며 중심적인 역할을 수행하게 되므로 다른 mv*패턴에 비해 코드 양이 더욱 많아집니다. 상대적으로 뷰모델의 코드는 줄어들고 보다 본인과 연결된 모델 통제에 집중할 수 있습니다. 기존 컨트롤러와 프리젠터에 있던 뷰관련 로직이 뷰로 옮겨지는 효과가 있지만 반대로 뷰로직의 공통로직을 둘 곳이 애매해져 별도의 통제 센터를 만들어야하고 개별 뷰의 복잡성이 증가하게 됩니다. 다른 곳보다 뷰의 코드가 증가하는 것이 전체 개발 분량에 큰 영향을 끼치게 되므로 이는 가벼운 문제가 아닙니다.
위의 내용이 미묘한 문구라 삭제했습니다. 대신 좀 더 정리해서 적어보았습니다 ^^
MVVM은 뷰모델과 뷰의 연결을 자동으로 해주는 추상화된 프레임웍이 필요합니다. 만약 자동으로 둘 사이를 바인딩해주는 프레임웍이 없다면 뷰의 변화와 상호작용이 뷰모델과 원활하게 연결되도록 하는 코드를 전부 작성해야합니다. 이렇게 되면 MVC에 뷰모델까지 추가되어 컨트롤러가 뷰와 뷰모델을 연결하는 책임까지도 수행하는 꼴이 되어버리는 경우가 많습니다.
IDE의 지원 등이 없는 상태에서 mvvvm을 쓰려면 상당한 수준의 추상화된 프레임웍이 도와줘야 합니다. 하지만 그럼에도 불구하고

  1. 뷰모델이 특정 모델에 대해 단일한 책임을 질 수 있고,
  2. 뷰에서 다양한 뷰모델과 결합하여 내용을 구성할 수 있어

굉장히 각광받고 있습니다. mvc나 mvp에서는 이 문제를 개발자가 직접 해결해야 하는데 비해 mvvm은 이를 구조적으로 강제하는 면이 있습니다.

결론

우선 mv*패턴의 기본적인 개요를 살펴봤습니다만, 이 정도로 이 패턴이 이해되거나 간단하지는 않습니다.
각 패턴마다 지옥이 가득히 펼쳐지기 때문에 실질적인 코드를 살펴보면서 뉴비분들에게 도움이 되는 친절한 안내를 진행해보죠.