MVC, MVP, MVVM은 모두 화면을 담당하는 View와 실제 데이터 및 비즈니스 로직을 분리하기 위한 아키텍처 패턴이다.
세 패턴의 차이를 단순히 “Controller냐 Presenter냐 ViewModel이냐”로 보면 구조만 외우게 된다. 실제 차이는 View가 어디까지 책임을 가지고 있는지, 상태를 누가 관리하는지, View와 중간 계층이 어떤 방식으로 통신하는지에 있다.
핵심 구조 비교
| 패턴 | 중간 계층 | 핵심 역할 | View와의 관계 |
|---|---|---|---|
| MVC | Controller | 입력을 받아 Model을 변경하고 View 흐름을 제어 | View가 Model을 직접 참조하는 형태가 흔함 |
| MVP | Presenter | View의 입력과 화면 갱신을 모두 중재 | Presenter가 View를 직접 제어 |
| MVVM | ViewModel | View에 필요한 상태와 명령을 제공 | Binding을 통해 View와 상태를 연결 |
핵심적인 차이는 화면 갱신을 누가 주도하느냐에 있다.
MVC에서는 View가 Model을 읽어 화면을 구성하는 구조가 흔하다.
MVP에서는 Presenter가 필요한 데이터를 가져온 뒤 View에게 무엇을 표시할지 직접 지시한다.
MVVM에서는 ViewModel의 상태가 변경되고, Data Binding이나 Reactive 시스템을 통해 View가 그 변화를 반영한다.
다만 이 구조는 프레임워크나 구현 방식에 따라 달라질 수 있다. MVC라고 해서 반드시 View가 Model을 직접 참조해야 하는 것도 아니고, MVVM이라고 해서 반드시 특정 형태의 Binding 기술을 사용해야 하는 것도 아니다.
MVC
MVC는 Model, View, Controller 세 영역으로 책임을 나눈다.
일반적인 흐름은 다음과 같다.
사용자 입력 -> Controller -> Model 변경/조회 -> View 렌더링
Controller는 사용자의 요청이나 입력을 받아 적절한 동작을 수행한다.
예를 들어 로그인이라면 Controller가 로그인 요청을 받고 인증 로직이나 서비스를 호출한 다음 결과에 따라 View를 결정할 수 있다.
여기서 중요한 부분은 MVC의 Model을 단순히 하나의 데이터 클래스라고 생각하면 안 된다는 것이다.
Model은 구현 방식에 따라 데이터뿐만 아니라 도메인 상태와 비즈니스 규칙까지 포함할 수 있다. 실제 프로젝트에서는 User, Order, Product처럼 여러 도메인 객체와 Repository, Service 등의 계층으로 나뉘기도 한다.
따라서 실제 프로젝트에 Model이라는 클래스 하나와 Controller 하나만 존재하는 것은 아니다.
기능이나 도메인에 따라 여러 객체가 존재하며, Controller 역시 화면이나 요청 단위로 여러 개가 만들어질 수 있다.
MVC의 특징
MVC의 가장 큰 장점은 구조가 단순하다는 것이다.
웹 서버나 전통적인 GUI 프로그램처럼 요청을 받아 처리하고 결과를 렌더링하는 구조에서는 상당히 자연스럽다.
반대로 화면의 상태가 복잡해지면 문제가 생길 수 있다.
View가 Model을 직접 참조하고 Controller까지 복잡하게 얽히기 시작하면 어느 코드가 상태를 변경하는지 추적하기 어려워진다.
결국 MVC 자체가 문제가 아니라 책임 경계가 무너지면서 View, Controller, Model 사이의 의존성이 증가하는 것이 문제다.
MVP
MVP에서는 Controller 대신 Presenter가 View와 Model 사이에 들어간다.
기본적인 흐름은 다음과 같다.
사용자 입력 -> View -> Presenter -> Model/Service -> Presenter -> View 갱신
여기서 Presenter의 역할이 MVC의 Controller와 조금 다르다.
Presenter는 단순히 입력을 전달하는 역할만 하는 것이 아니라 화면에 어떤 상태를 표시해야 하는지까지 판단한다.
예를 들어 로그인 결과가 성공이면 로그인 화면을 닫고 메인 화면을 보여주고, 실패하면 오류 메시지를 표시하도록 Presenter가 결정할 수 있다.
View는 가능한 한 화면을 그리는 역할에 집중한다.
예를 들어 다음과 같은 식이다.
Presenter -> View.ShowLoginSuccess()
Presenter -> View.ShowError(message)
View는 전달받은 명령에 따라 실제 UI를 변경한다.
이 때문에 MVP에서는 View와 Presenter 사이의 인터페이스를 명확하게 정의하는 경우가 많다.
MVP에서 DTO를 사용할 수 있는 이유
MVP를 설명하면서 “DTO가 Model을 대체한다”고 표현하면 조금 위험하다.
DTO는 어디까지나 데이터 전달 객체다.
Presenter가 DTO를 받아 View에 필요한 값을 전달하는 것은 충분히 가능하다.
예를 들어 서버에서 다음과 같은 DTO가 내려왔다고 하자.
LoginResultDto -> UserName, ProfileImage, IsAdmin
Presenter는 이 데이터를 해석하고 View에 필요한 형태로 전달할 수 있다.
하지만 그렇다고 DTO가 Model과 동일한 역할을 하는 것은 아니다.
Model이 도메인 규칙과 상태를 포함하고 있다면 DTO는 그것을 대체하지 못한다.
따라서 정확하게 표현하면 MVP에서는 View와 Model 사이의 데이터 전달 과정에서 DTO를 적극적으로 사용할 수 있으며, 화면에 필요한 단순 데이터만 필요하다면 Presenter가 DTO를 직접 다루는 구조도 가능하다 정도가 적절하다.
MVP의 장점
가장 큰 장점은 View와 비즈니스 처리의 분리가 명확하다는 것이다.
특히 UI 테스트가 중요한 환경에서는 Presenter의 로직을 View와 분리해서 테스트하기 쉽다.
하지만 문제가 하나 있다.
화면이 복잡해질수록 Presenter가 모든 것을 처리하게 된다.
사용자 입력 처리, 데이터 조회, 상태 판단, 화면 갱신, 오류 처리까지 Presenter에 몰리면 결국 거대한 Presenter가 만들어진다.
LoginPresenter.cs가 수천 줄이 되는 상황도 충분히 발생할 수 있다.
결국 MVP 역시 Presenter 내부의 책임을 적절한 Service나 UseCase 등으로 분리해야 한다.
MVVM
MVVM은 Model, View, ViewModel로 구성된다.
핵심은 Presenter처럼 View를 직접 조작하지 않는다는 점이다.
일반적인 흐름은 다음과 같다.
사용자 입력 -> View -> Command/ViewModel -> 상태 변경 -> Data Binding -> View 갱신
ViewModel은 View에 필요한 데이터를 상태 형태로 제공한다.
예를 들어 다음과 같은 상태가 있다고 하자.
UserName
IsLoggedIn
IsLoading
ErrorMessage
View는 이 값들과 Binding되어 있을 수 있다.
ViewModel에서 IsLoading의 값을 변경하면 Binding 시스템이 이를 감지하고 View의 로딩 UI를 변경한다.
따라서 Presenter 방식처럼
Presenter -> View.ShowLoading()
Presenter -> View.HideLoading()
처럼 View를 직접 제어할 필요가 없다.
ViewModel은 “로딩 상태다”라는 상태만 변경하고 실제 화면 표현은 View가 담당한다.
MVVM에서 가장 중요한 개념은 Binding이다
MVVM의 핵심을 단순히 “ViewModel이라는 클래스를 사용하는 패턴”으로 보면 안 된다.
중요한 것은 View와 ViewModel 사이의 상태 동기화 방식이다.
전형적인 구조에서는 다음과 같은 관계가 만들어진다.
View <-> Data Binding <-> ViewModel
ViewModel의 상태가 변경되면 View가 이를 반영하고, 반대로 View의 입력이 ViewModel의 속성이나 Command로 전달될 수 있다.
이 구조가 제대로 동작하려면 단순한 데이터 필드만으로는 부족하다.
상태 변경을 감지할 수 있는 Observable Property, Event, Reactive Stream 등의 메커니즘이 필요할 수 있다.
다만 이것이 Model 자체에 반드시 필요한 것은 아니다.
MVVM에서 반응형 상태를 담당하는 주체는 일반적으로 ViewModel이다.
Model이나 DTO가 반드시 변경 이벤트를 가지고 있어야 하는 것은 아니다.
이 부분은 MVVM을 이해할 때 꽤 중요한 차이다.
MVC와 MVP의 차이
둘은 상당히 비슷해 보인다.
실제로 많은 프로젝트에서 Controller와 Presenter의 역할이 겹치기도 한다.
차이를 단순화하면 다음과 같다.
MVC는 View가 Model과 상호작용하는 구조가 비교적 허용된다.
Controller -> Model -> View
반면 MVP에서는 Presenter가 View와 Model 사이를 적극적으로 중재한다.
View <-> Presenter <-> Model
따라서 MVP에서는 View가 Model의 내부 구조를 알 필요가 없도록 만들기 쉽다.
대신 Presenter와 View 사이의 결합도가 올라간다.
MVP와 MVVM의 차이
둘의 차이는 더 명확하다.
MVP에서는 Presenter가 View를 직접 제어한다.
Presenter -> View
예를 들어
View.ShowUserName(name)
View.ShowError(message)
같은 호출이 가능하다.
MVVM에서는 ViewModel이 View를 직접 호출하지 않는다.
대신 상태를 변경한다.
ViewModel -> State 변경 -> Binding -> View
따라서 ViewModel은 특정 UI 구현에 대한 의존성을 줄일 수 있다.
이 구조 때문에 MVVM은 UI 프레임워크에서 제공하는 Data Binding이나 Reactive 시스템과 궁합이 좋다.
ViewModel이 많아지는 문제
MVVM을 적용하면 화면마다 ViewModel을 만드는 구조가 자연스럽게 나온다.
예를 들어
LoginView -> LoginViewModel
InventoryView -> InventoryViewModel
ShopView -> ShopViewModel
QuestView -> QuestViewModel
같은 구조가 만들어질 수 있다.
화면이 많아지면 ViewModel도 자연스럽게 많아진다.
하지만 이것 자체가 문제라고 보기는 어렵다.
View와 ViewModel이 1:1이라는 규칙이 있는 것도 아니고, 모든 비즈니스 로직을 ViewModel에 집어넣어야 하는 것도 아니다.
오히려 복잡한 프로젝트에서는 다음과 같이 책임을 분리하는 편이 낫다.
View -> ViewModel -> UseCase/Service -> Repository -> Data Source
ViewModel은 화면 상태와 사용자 입력을 관리하고, 실제 업무 규칙은 UseCase나 Service가 담당하도록 만든다.
그러면 ViewModel이 지나치게 비대해지는 것을 막을 수 있다.
전역 상태 관리와 MVVM
화면마다 ViewModel을 만들다 보면 여러 화면에서 공유해야 하는 상태가 생긴다.
예를 들어 로그인 사용자 정보, 인벤토리, 설정값, 서버 연결 상태 같은 데이터다.
이런 데이터를 각각의 ViewModel에서 따로 관리하면 상태 동기화 문제가 발생한다.
그래서 프로젝트 성격에 따라 전역 Store나 상태 관리 시스템을 함께 사용하기도 한다.
다만 Redux나 Pinia 같은 상태 관리 도구를 사용한다고 해서 MVVM의 ViewModel이 필요 없어지는 것은 아니다.
각각 해결하려는 문제가 다르다.
ViewModel은 특정 View에서 필요한 상태와 동작을 표현하는 역할에 가깝고, 전역 Store는 여러 화면에서 공유되는 상태를 관리하는 역할에 가깝다.
로그인 기능으로 비교
같은 로그인 기능을 세 패턴으로 생각해보면 차이가 더 명확하다.
MVC
사용자 입력 -> Controller -> 인증 처리 -> Model 변경 -> View 렌더링
Controller가 요청을 받아 인증을 수행하고 결과에 따라 View를 선택하거나 Model을 변경한다.
View는 필요한 데이터를 Model에서 읽어 화면을 구성할 수 있다.
MVP
사용자 입력 -> View -> Presenter -> 인증 처리 -> Presenter -> View 갱신
Presenter가 인증 결과를 판단하고 View에 직접 명령한다.
ShowLoginSuccess()
ShowLoginFailure(message)
처럼 화면에 무엇을 표시할지를 Presenter가 결정한다.
MVVM
사용자 입력 -> View -> ViewModel Command -> 인증 처리 -> 상태 변경 -> Binding -> View 갱신
ViewModel은
IsLoggedIn = true
IsLoading = false
ErrorMessage = ""
같은 상태를 변경한다.
View는 Binding을 통해 변경된 상태를 반영한다.
여기서 중요한 것은 로그인 암호화 같은 핵심 보안 로직을 Controller, Presenter, ViewModel 안에 무조건 넣는 것이 아니다.
실제 프로젝트라면 인증 서비스나 UseCase 등의 별도 계층으로 분리하는 편이 훨씬 낫다.
결국 세 패턴의 차이는 책임 배치다
MVC, MVP, MVVM을 외우는 가장 좋은 방법은 약어를 외우는 것이 아니다.
“화면과 데이터를 누가 어떻게 연결하는가?”를 보는 것이다.
MVC는 Controller가 입력과 흐름을 제어하고 View와 Model 사이의 직접적인 상호작용이 비교적 허용된다.
MVP는 Presenter가 View와 Model 사이를 강하게 중재한다. View는 수동적인 UI에 가까워진다.
MVVM은 ViewModel이 화면에 필요한 상태를 제공하고 Binding을 통해 View와 상태를 동기화한다.
정리하면 다음과 같다.
MVC -> Controller가 흐름을 제어
MVP -> Presenter가 View를 직접 제어
MVVM -> ViewModel이 상태를 관리하고 Binding으로 View를 갱신
그리고 실무에서는 이 세 가지를 교과서처럼 그대로 사용하는 경우보다 프로젝트 특성에 맞게 섞어서 사용하는 경우가 많다.
중요한 것은 패턴의 이름이 아니다.
변경이 발생했을 때 어디의 코드를 수정해야 하는가, 각 객체가 어디까지 책임져야 하는가, 그리고 의존성이 어느 방향으로 흘러가는가.
결국 좋은 아키텍처의 기준은 MVC냐 MVP냐 MVVM이냐가 아니라 변경 비용을 얼마나 낮출 수 있느냐에 있다.
난 개인적으로 MVP가 깔끔해서 가장 좋다.