최근 GoF 디자인 패턴 북스터디를 진행하면서, 문득 이런 생각이 들었습니다.
"GoF 패턴을 공부하는 것도 좋지만, 그보다 더 오래되고, 더 본질적인 개념부터 다시 짚어보는 건 어떨까?"
그래서 이번 글에서는, 소프트웨어 아키텍처의 기초이자, 지금까지도 강한 영향을 끼치고 있는
MVC(Model-View-Controller) 패턴을 프론트엔드 개발자의 시선에서 다시 들여다보려 합니다.
이번 글에서는
살펴보겠습니다.
흔히 MVC를 서버 아키텍처의 일부로 생각하곤 합니다. 하지만, MVC는 사실 클라이언트 측 GUI 애플리케이션을 위해 탄생했습니다.
때는 1979년, Xerox PARC — 컴퓨터 역사상 가장 전설적인 연구소 중 하나 — 에서 트리그베 린스카우그(Trygve Reenskaug)는 고민에 빠졌습니다.
"점점 복잡해지는 사용자 인터페이스를, 어떻게 깔끔하게 관리할 수 있을까?"
그 고민 끝에 탄생한 것이 바로 MVC 패턴이었습니다.
MVC가 태어난 배경에는 Smalltalk라는 프로그래밍 언어가 있었습니다.
Smalltalk는 1970년대 초 Xerox PARC에서 개발된 언어로, 객체 지향 프로그래밍(OOP) 개념을 완성도 있게 구현한 시스템입니다.
또한 Smalltalk는 세계 최초로 그래픽 사용자 인터페이스(GUI)를 도입한 언어이기도 합니다.
창, 버튼, 입력 필드 같은 다양한 컴포넌트를 다루는 복잡한 사용자 인터페이스 환경 속에서, 구조적이고 체계적인
복잡한 UI를 체계적으로 관리할 필요성 속에서, 자연스럽게 MVC 아키텍처가 등장하게 된 것이죠.
당시 클라이언트 애플리케이션은 급격히 복잡해지고 있었습니다.
하지만 초기 프로그램 구조는 여전히 데이터 처리, 입력 처리, 화면 출력이 뒤엉킨 스파게티 코드였습니다. 🍝
린스카우그는 이를 해결하기 위해 "관심사의 분리" 원칙을 도입합니다.
이렇게 각자의 역할을 명확히 분리한 덕분에, UI는 훨씬 구조적이고 유지보수하기 쉬운 형태로 발전할 수 있었습니다.

MVC의 핵심은 "각자의 역할을 명확히 나누어 복잡성을 줄이는 것"이었습니다.
시대가 흐르면서, MVC는 서버 사이드에서도 큰 인기를 끌었습니다.
특히, Ruby on Rails, Spring MVC, ASP.NET MVC 같은 프레임워크들은 MVC를 기반으로 웹 애플리케이션을 설계하는 표준이 되었습니다.
간단한 Express.js 예제를 볼까요?
1// routes/user.js
2const express = require('express');
3const router = express.Router();
4const UserModel = require('../models/user');
5
6router.get('/users', async (req, res) => {
7 const users = await UserModel.getAllUsers(); // Model 호출
8 res.render('users', { users }); // View로 데이터 전달
9});
10
11module.exports = router;여기서도 똑같이
MVC를 통해 서버 코드 역시 구조적이고 명확하게 유지할 수 있었습니다.
시간이 흐르면서 웹은 단순한 정보 제공용 페이지를 넘어, 상호작용이 핵심인 복잡하고 역동적인 애플리케이션으로 진화했습니다.
웹은 점점 데스크톱 애플리케이션에 가까워지고 있었습니다. 하지만 이 변화는 기존 서버 중심의 MVC 아키텍처로는 대응하기 어려운 문제를 낳았습니다.
결국, 복잡한 프론트엔드 애플리케이션을 다루기에는 기존 MVC만으로는 부족해졌습니다.
MVC 구조는 결국, 규모가 커지고 복잡해진 프론트엔드 애플리케이션을 제대로 다루기에는 한계가 있었습니다.
이런 한계를 극복하기 위해 등장한 것이 바로 SPA(Single Page Application) 아키텍처입니다.
한 번의 페이지 로딩 이후, 사용자 입력에 따라 클라이언트에서 필요한 부분만 동적으로 갱신하는 방식이죠.
하지만 SPA 시대가 열리면서, 웹 애플리케이션은 이제 "페이지 단위"가 아니라 "컴포넌트 단위" 로 동작하고,
컴포넌트 간 상태 관리가 새로운 과제로 떠올랐습니다. 이를 해결하기 위한 새로운 접근이 필요해졌습니다.

MVVM(Model-View-ViewModel)은 컴포넌트 기반 SPA에서 발생하는 복잡성을 해결하려는 시도였습니다.
특징은 View와 ViewModel 간의 양방향 바인딩입니다. (Angular, Vue 2.x)
하지만 앱이 커지면서 양방향 바인딩이 오히려 복잡성을 증가시키는 부작용이 나타났습니다.
Flux는 단순한 상태 관리 패턴이 아니라, 앱의 데이터 흐름 자체를 명확하게 규정한 아키텍처입니다.
핵심 아이디어: "데이터는 항상 한 방향으로만 흐른다."
구조는 다음과 같습니다.
View → Action → Dispatcher → Store → View
단방향 흐름 덕분에 상태 추적이 쉬워지고, 버그를 줄이고, 복잡한 앱도 구조적으로 관리 할 수 있게 되었습니다.
이전까지는 "어디서 뭐가 바뀌었는지" 알기 어려웠지만,
Flux에서는 상태가 바뀌는 경로가 항상 Action → Dispatcher → Store 순서로만 진행되기 때문에, 앱의 동작을 추적하고 이해하는 게 수월해졌습니다.
요즘은 React 같은 라이브러리를 보면,
Controller 역할까지 함께 수행하는 경우가 많습니다.
또한, 전역 상태를 관리하는 Redux, Zustand 같은 라이브러리들은 자연스럽게 Model 역할을 담당합니다.
프레임워크와 기술은 변했지만, 변하지 않은 원칙이 있습니다. 바로 "관심사의 분리" 입니다.
현대 프론트엔드 프레임워크들은 더 이상 고전적인 MVC 구조를 그대로 따르진 않습니다.
하지만 여전히 소프트웨어 아키텍처의 핵심 원칙인 "관심사의 분리" 와 "구조적 설계" 는 살아 숨 쉬고 있습니다.
예를 들어,
이처럼 현대의 프레임워크와 라이브러리들은 모두 "관심사의 분리"를 다양한 방식으로 구현하고 있고,
결국 현대의 다양한 프레임워크들도, 모두 MVC 철학을 현대적으로 재해석한 결과물입니다.
MVC는 단순히 과거의 유물이 아닙니다. 오늘날처럼 복잡하고 역동적인 UI를 설계할 때, 여전히 "구조적으로 사고하는 틀" 이 되어줍니다.
현대 프론트엔드 개발에서 흔히 사용하는 아키텍처 패턴들 - Flux, MVVM, 상태 관리 라이브러리들은 MVC의 철학을 현대적으로 재해석한 결과물입니다.
따라서 MVC를 이해하는 것은 단순히 옛 개념을 공부하는 것이 아닙니다. 더 나은 설계, 더 견고한 시스템, 더 명확한 역할 분담을 위해 프론트엔드 개발자로서 반드시 갖춰야 할 사고방식을 익히는 과정입니다.
MVC를 제대로 이해하고 있다면, 어떤 새로운 기술이 등장하더라도 그 기반이 되는 원칙을 꿰뚫어보고 현명하게 선택하고 설계할 수 있게 될 것입니다.