MVC 제대로 알기

Seoyong Lee·2025년 2월 1일
6

개발 공부

목록 보기
23/23

2025년에 React로 프론트엔드 개발을 하면서 가장 제대로 이해하기 어려운 개념 중의 하나가 바로 MVC가 아닐까 합니다. 인터넷의 글들은 대부분 ‘MVC, MVP, MV* 구현하기’와 같이 본질보다는 구현에 집중하는 경우가 대부분이라 제대로 이해할 기회가 없었던 것도 한몫한다고 생각합니다. 그러던 중 우연히 최근 출간된 ‘자바스크립트 + 리액트 디자인 패턴’ 서적을 읽게 되었고 여기서 언급된 마틴 파울러의 ‘GUI Architectures’ 글(매우 길고 난해하지만)을 읽고 MVC의 개념에 대해 조금 더 다가갈 수 있었습니다. 마지막으로 '김코딩’님의 블로그 글은 MV* 패턴 전체에 대한 이해에 많은 도움이 되었습니다. 부족하지만 제가 이해한 버전을 공유해 드림으로써 다른 분들이 MV* 을 이해하는 데 조금이나마 도움이 되길 바랍니다.

MVC란?

MVC는 다음과 같이 Model-View-Controller로 관심사를 분리하여 설계하는 설계 패턴(Archictecture Pattern)으로 알려져 있습니다.

  • Model: 비즈니스 관련 도메인 모델
  • View: 모델의 정보를 화면에 표현
  • Controller: 사용자 상호작용을 처리하고 로직으로 연결

위 구조만 보면 MVC는 각 요소를 정리하는 방법에 대한 패턴일 것 같지만 마틴 파울러에 따르면 MVC의 본질은 Seperated Presentation입니다.

The idea behind Separated Presentation is to make a clear division between domain objects that model our perception of the real world, and presentation objects that are the GUI elements we see on the screen. Domain objects should be completely self contained and work without reference to the presentation, they should also be able to support multiple presentations, possibly simultaneously. This approach was also an important part of the Unix culture, and continues today allowing many applications to be manipulated through both a graphical and command-line interface.

분리된 프레젠테이션(Separated Presentation)의 개념은, 현실 세계를 모델링하는 도메인 객체(Domain Objects)와 화면에서 보이는 GUI 요소인 프레젠테이션 객체(Presentation Objects)를 명확하게 분리하는 것입니다. 도메인 객체(Domain Objects)는 완전히 독립적(self-contained)이어야 하며, 프레젠테이션과의 직접적인 참조 없이 동작 가능해야 합니다. 또한, 여러 개의 프레젠테이션을 동시에 지원할 수 있어야 합니다. 이러한 접근 방식은 유닉스(Unix) 문화에서도 중요한 개념이었으며, 오늘날에도 많은 애플리케이션이 GUI와 명령줄 인터페이스(Command-Line Interface, CLI)를 동시에 지원할 수 있도록 합니다.

Martin Fowler - GUI Architectures

위 관점에서 MVC를 생각해 보면 MVC는 각 요소(뷰, 모델, 컨트롤러)의 구체적인 구현보다는 뷰-모델의 관계에 집중하여 만들어졌다는 것을 알 수 있습니다. 그렇다면 MVC가 최초에 뷰(UI)와 모델(도메인)의 분리를 통해 해결하려는 문제는 무엇이었을까요?

이를 이해하기 위해서는 먼저 1970년대에 Xerox PARC에서 MVC를 최초로 공식화했던 Trygve Reenskaug(트리그베 렌스카우그)글(MVC XEROX PARC 1978-79)을 살펴볼 필요가 있습니다.

The essential purpose of MVC is to bridge the gap between the human user's mental model and the digital model that exists in the computer. The ideal MVC solution supports the user illusion of seeing and manipulating the domain information directly. The structure is useful if the user needs to see the same model element simultaneously in different contexts and/or from different viewpoints.

MVC의 본질적인 목적은 인간 사용자의 정신적 모델과 컴퓨터에 존재하는 디지털 모델 간의 격차를 좁히는 것입니다. 이상적인 MVC 솔루션은 사용자가 도메인 정보를 직접 보고 조작하는 환상을 지원합니다. 이 구조는 동일한 모델 요소를 서로 다른 컨텍스트 및/또는 관점에서 동시에 볼 필요가 있을 때 유용합니다.
MVC
XEROX PARC 1978-79

위와 같이 최초 MVC는 컴퓨터 모델과 인간의 정신적 모델 사이의 차이를 인정하고 이를 서로 다르게 접근하도록 하여 차이를 좁히는 것을 제안합니다. 컴퓨터 관점의 복잡한 데이터 셋(모델)을 인간의 관점에서 쉽게 조작하도록 돕는(UI) 문제를 해결하기 위해 고안된 것이었죠. 이 시점은 GUI 자체가 흔하지 않았던 시대였으며, UI 작업을 체계적으로 수행하려는 최초의 시도 중 하나였다는 점을 생각한다면 MVC는 당시 관점으로도 혁신적인 개념이었습니다.

그렇다면 이러한 분리를 통해 개발 관점에서 얻는 효과는 무엇일까요? 많은 사람들이 ‘코드의 재사용’이 MVC를 통해 얻는 가장 큰 장점이라고 생각하는 경우가 많습니다. 그러나 김코딩님의 통찰력 있는 글에 따르면 MVC는 '도메인'을 보호하기 위해 이를 나머지와 분리한 것이 핵심으로, 분리된 나머지 계층의 동기화 문제를 해결하려다 여러 패턴이 파생되었다고 설명합니다.

이어져 온 흐름을 보면 전체 역사를 관통하는 한 가지가 눈에 띈다. 너무나 당연한 이야기지만 도메인(도메인 모델)과 UI는 항상 존재해왔다는 사실이다. 간혹 MVC를 이야기하면서 코드 재사용성을 언급하는 경우를 본다. 코드 재사용성 증대는 도메인을 보호하고 관심사를 분리하는 과정에서 우연히 덤으로 얻은 이득일 뿐 MVC의 본질은 아니다.

도메인을 보호하기 위해서 관심사를 분리하였다. 이 과정에서 UI와 도메인 계층이 만들어졌고, 두 계층에 존재하는 상태를 동기화해야 하는 문제가 생겼다. 시스템이 놓인 맥락에서 더 나은 동기화 전략을 만들어 문제를 해결한다. MVC에서 WebMVC로 이어지는 역사는 도메인과 UI의 분리, 분리로 인해 만들어진 문제를 해결, 그리고 더 나은 해결책을 찾는 과정 속에서 발전했다.

M-V-Whatever 정리 - 1.MVC

MVC는 인간 관점에서 UI를 통해 편리하게 데이터를 조작할 수 있도록 돕는 면도 있지만 컴퓨터 관점에서 도메인(모델)을 사용자 인터페이스와 분리해 순수한 상태로 보호하려는 측면도 중요하다고 보는 것입니다.

지금까지 살펴보았던 내용을 바탕으로 MVC를 다시 정리해 보면 다음과 같습니다.

  • MVC는 최초 인간과 컴퓨터의 차이를 극복하고 효과적으로 데이터를 컨트롤하기 위해 모델과 뷰를 분리하면서 시작되었습니다.
  • 이후 모델과 뷰 사이의 동기화 문제를 해결하기 위해 여러 개념이 정립되었고 컨트롤러, 프레젠터, 뷰모델 등의 개념이 등장하게 됩니다.

MVC와 리액트

그렇다면 React는 MVC와 어떤 사이일까요? 결론부터 말하자면 리액트는 전통적인 MVC와 거리가 있습니다. 이를 이해하기 위해선 MVC 패턴을 다시 데스크탑 기반의 전통적인 MVC와 Web MVC로 구분해야 합니다.

1970년대 데스크탑 어플리케이션을 구현하는 과정에서 처음 도입된 전통적인 MVC는 옵저버(Observer)를 기반으로 합니다. 모든 뷰(Views)와 컨트롤러(Controllers)는 모델(Model)을 관찰(Observe)하며 모델이 변경되면 뷰가 자동으로 업데이트 되는 방식으로 구현되었습니다.

그러나 이후 도입된 Web MVC는 다음과 같이 서버-클라이언트 구조를 기반으로 합니다.

  • Model: 데이터 소스 (ex. Java Beans)
  • View: 서버에서 브라우저로 전달되는 완성된 뷰 (ex. JSP)
  • Controller: 데이터 조회 등 요청 전달 (ex. Servlet)

Web MVC는 기존 MVC와 조금 다른 특성을 가집니다.

  • 기술적인 이유로 Model과 View 사이에 Observer Pattern을 사용하는 것이 어렵습니다
  • Controller가 사용자의 인터랙션이 아닌 HTTP 요청을 처리합니다

Web MVC의 구조는 대부분 서버 사이드에서 구현되었기 때문에 현대의 SPA와는 완전히 다른 개념이었습니다. 그렇다면 리액트를 MVC 관점에 대입해 보면 ‘뷰(View)’를 담당한다고 볼 수 있지 않을까요?

엄밀히 말하면 리액트는 서버에서 만든 ‘뷰’를 그대로 보여주는 것이 아닌, ‘데이터’를 받아 브라우저에서 생성하기 때문에 전통적인 의미의 MVC 프레임워크로 볼 수 없습니다. 그러나 Next.js의 SSR을 이용한다면 비슷하게 구현할 수는 있습니다.

또한 React를 이용해 작은 MVC를 구현한다고 하면 다음과 같이 생각할 수도 있습니다.

  • Model: 비동기 데이터
  • View: 컴포넌트
  • Controller: Hooks

이러한 구분은 개념적인 비유는 될 수 있지만 엄밀한 의미의 MVC와는 차이가 있습니다. 그렇다면 React가 아니더라도 JS로 엄밀한 MVC를 구현하는 것은 가능할까요?

MVC와 JS

실제로 과거 순수한 MVC 패턴에 충실한 JS 프레임워크가 존재합니다. 바로 Maria.js로 레포의 Todo 예제를 보면 전통적인 Smalltalk-80 MVC의 구현을 쉽게 살펴볼 수 있습니다.

// TodoModel.js
maria.Model.subclass(checkit, 'TodoModel', {
    attributes: {
        content: {
            type: 'string',
            trim: true
        },
        done: {
            type: 'boolean'
        }
    }
});

모델의 경우 content 및 일정의 완료(done) 상태와 같은 attribute로 이루어져 있습니다.

// TodoInputController.js
maria.Controller.subclass(checkit, 'TodosInputController', {
    properties: {
        onFocusInput: function () {
            this.onKeyupInput();
        },
        onBlurInput: function () {
            this.getView().setPending(false);
        },
        onKeyupInput: function () {
            var view = this.getView();
            view.setPending(!checkit.isBlank(view.getInputValue()));
        },
        onKeypressInput: function (evt) {
            if (evt.keyCode == 13) {
                var view = this.getView();
                var value = view.getInputValue();
                if (!checkit.isBlank(value)) {
                    var todo = new checkit.TodoModel();
                    todo.setContent(value);
                    this.getModel().add(todo);
                    view.clearInput();
                }
            }
        }
    }
});

컨트롤러는 FocusInput, KeyPressInput과 같은 사용자 인터랙션을 처리합니다.

// TodoInputView.js
maria.ElementView.subclass(checkit, 'TodosInputView', {
    uiActions: {
        'focus    .content': 'onFocusInput'   ,
        'blur     .content': 'onBlurInput'    ,
        'keyup    .content': 'onKeyupInput'   ,
        'keypress .content': 'onKeypressInput'
    },
    properties: {
        getInputValue: function () {
            return this.find('.content').value;
        },
        clearInput: function () {
            this.find('.content').value = '';
        },
        setPending: function (pending) {
            aristocrat[pending ? 'addClass' : 'removeClass'](
                this.find('.TodosInput'), 'TodosInputPending');
        }
    }
});

뷰는 content의 value 등을 UI로 보여줍니다.

References

Martin Fowler - GUI Architectures
Trygve Reenskaug - MVC XEROX PARC 1978-79
김코딩 님이 잘하고 싶어 만든 블로그 - MV-Whatever 정리
애디 오스마니 - 자바스크립트 + 리액트 디자인 패턴
Peter Michaux - Maria.js

0개의 댓글

관련 채용 정보