# Model-View-Controller (MVC) 구조
Model-View-Controller(MVC) 구조는 소프트웨어 설계 패턴 중 하나로, 사용자 인터페이스(UI)와 비즈니스 로직의 관심사를 분리하여 유지보수성과 확장성을 높이는 것을 목표로 한다. 이 구조에서 세 가지 핵심 구성 요소는 서로 다른 역할을 맡아 공동으로 시스템의 동작을 구현한다.
## 개요
- Model: 데이터 모델 및 비즈니스 로직을 담당한다. 상태(state) 관리와 데이터 유효성 검사, 데이터 저장소와의 상호 작용을 포함한다.
- View: 화면에 표시되는 UI 요소를 담당한다. 사용자가 보는 시각적 출력을 구성하고, Model의 상태를 반영하여 렌더링한다.
- Controller: 입력을 해석하고 모델과 뷰 간의 중재자 역할을 한다. 사용자 동작으로부터 요청을 받고, 이를 바탕으로 Model을 수정하거나 View를 갱신한다.
MVC의 주된 아이디어는 세 가지 관심사를 분리함으로써 각 구성요소를 독립적으로 개발, 테스트, 교체할 수 있도록 하는 데 있다. 예를 들어 UI의 형태를 바꾸고자 할 때 Model의 비즈니스 로직에 영향을 주지 않고 View만 수정할 수 있다.
## 구성 요소
- Model
- 데이터 구조와 상태를 보유한다.
- 비즈니스 규칙, 데이터 유효성 검사, 데이터 저장/로딩 로직을 포함한다.
- 일반적으로 View에 직접 의존하지 않는다.
- View
- 사용자에게 보이는 화면 구성 요소를 담당한다.
- Model의 상태를 바탕으로 화면을 렌더링한다.
- 필요 시 View는 Controller의 입력 이벤트를 통해 Model에 변경을 요청한다.
- Controller
- 사용자의 입력 이벤트를 수집하고 해석한다.
- 요청에 따라 Model의 상태를 수정하거나 View의 렌더링을 지시한다.
- 비즈니스 로직의 일부를 Model과 협력해 수행할 수 있다.
## 상호 작용 흐름
일반적인 MVC 흐름은 다음과 같다.
- 사용자가 View에서 입력을 제공한다(예: 버튼 클릭, 폼 제출).
- Controller가 이 입력을 해석하고 필요한 작업을 결정한다.
- Controller가 Model을 업데이트한다(또는 Model의 메서드를 호출한다).
- Model의 상태가 변경되면 View는 이를 반영해 화면을 재렌더링하거나, View는 Model의 상태를 구독하여 자동으로 갱신된다.
- 필요 시 Controller가 View에 재렌더링을 지시한다.
참고로, 일부 구현에서는 View가 Model의 상태를 직접 구독(Observer 패턴)하고, Model은 변경 이벤트를 발행한다. 이 경우 Controller의 개입은 최소화될 수 있다.
## 구현 방식과 변형
- 서버 사이드 MVC
- 전형적인 웹 프레임워크에서 사용된다. 예: Rails, Django(MTV 구조로도 불리지만 MVC의 원리와 유사), ASP.NET MVC, Laravel 등.
- 요청(request)을 Router가 해석하고, Controller가 특정 Model에 대한 동작을 수행한 뒤 View를 선택해 응답을 생성한다.
- 클라이언트 사이드 MVC
- JavaScript 기반의 UI에서 Model, View, Controller가 브라우저에서 상호 작용한다.
- View는 DOM 조작에 집중하고, Controller는 사용자 이벤트를 처리하며 Model의 상태를 갱신한다.
- MVC의 변형
- HMVC(Hierarchical MVC): MVC를 계층적으로 확장해 모듈 간 재사용성과 독립성을 높인 구조.
- MVC2, ASP.NET MVC 등 프레임워크별 구현 차이로 인해 라우팅, 데이터 바인딩 방식에 차이가 있다.
- MV* 계열과의 비교: MVC는 명확한 세 가지 역할을 강조하지만, MVVM(MVVM), MVP(MVP) 등은 View-Model 또는 Presenter를 통해 View와 Model 간의 결합을 다르게 관리한다.
## 장점과 한계
- 장점
- 관심사 분리로 유지보수성과 확장성이 향상된다.
- 테스트 용이성 증가: Model과 Controller를 독립적으로 테스트할 수 있다.
- UI의 재사용성 증가: View를 교체해도 Model의 비즈니스 로직에 영향을 주지 않는다.
- 한계
- 규모가 작은 애플리케이션에서 과도한 아키텍처로 인한 오버헤드가 발생할 수 있다.
- View와 Controller 간의 경계가 모호해지는 경우가 있어 설계 원칙을 명확히 정의해야 한다.
- 프런트엔드 현대화에 따라 MVVM이나 MVI와 같은 변형이 더 적합할 수 있는 상황이 존재한다.
## 실무 적용 시 고려사항
- 도메인 복잡도: 도메인 로직이 복잡할수록 MVC의 분리 이점이 더 커진다.
- 테스트 전략: Model의 비즈니스 로직과 Controller의 입력 처리 로직에 대한 테스트를 분리한다.
- 팀의 경험과 프레임워크 생태계: 사용 중인 프레임워크의 MVC 구현 방식과 생태계에 맞춘 설계 패턴을 따르는 것이 유지보수에 유리하다.
- 프런트엔드와 백엔드의 경계: 양쪽에서 MVC 원칙을 어떻게 적용할지에 대한 정책을 수립한다.
## 간단한 예시
다음은 간단한 웹 애플리케이션에서의 MVC 흐름을 의사코드로 나타낸 예시이다.
```pseudo
// Model
class TodoModel:
todos = []
function createTodo(text):
todo = { id: generateId(), text: text, done: false }
todos.append(todo)
notifyObservers("change")
function getAllTodos():
return todos
function setTodoDone(id, done):
todo = findTodoById(id)
if todo:
todo.done = done
notifyObservers("change")
// View
class TodoView:
function render(todos):
// DOM에 todos를 표시
updateTodoList(todos)
function bindController(controller):
// UI 이벤트와 Controller의 메서드를 바인딩
onAddButtonClick -> controller.addTodo
onTodoDoneToggle -> controller.toggleTodo
// Controller
class TodoController:
constructor(model, view):
this.model = model
this.view = view
this.view.bindController(this)
this.model.addObserver("change", () => this.view.render(this.model.getAllTodos()))
function addTodo(text):
this.model.createTodo(text)
function toggleTodo(id):
current = this.model.getTodoById(id)
this.model.setTodoDone(id, !current.done)
```
이 예시는 MVC의 기본 흐름을 간략히 보여 주며, 실제 구현은 사용되는 프레임워크의 API와 패턴에 따라 달라질 수 있다.
---
관련 문서: [[MVC 구성요소 심화]], [[MVC 및 MVVM 비교]]
[[소프트웨어 설계 패턴]]