# 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 비교]] [[소프트웨어 설계 패턴]]