MVC
Model-View-Controller
MVC는 애플리케이션을 Model(데이터), View(화면), Controller(로직)로 분리하는 소프트웨어 설계 패턴입니다. 관심사 분리를 통해 유지보수성과 테스트 용이성을 높이며, Ruby on Rails, Django, Spring 등에서 채택됩니다.
Model-View-Controller
MVC는 애플리케이션을 Model(데이터), View(화면), Controller(로직)로 분리하는 소프트웨어 설계 패턴입니다. 관심사 분리를 통해 유지보수성과 테스트 용이성을 높이며, Ruby on Rails, Django, Spring 등에서 채택됩니다.
MVC(Model-View-Controller)는 1979년 Trygve Reenskaug가 Smalltalk에서 처음 도입한 소프트웨어 아키텍처 패턴입니다. 애플리케이션을 세 가지 핵심 컴포넌트로 분리하여 관심사의 분리(Separation of Concerns)를 실현하고, 코드의 재사용성과 유지보수성을 높입니다.
Model은 애플리케이션의 데이터와 비즈니스 로직을 담당합니다. 데이터베이스와의 상호작용, 유효성 검사, 데이터 처리 규칙을 포함합니다. View는 사용자 인터페이스를 담당하며, Model의 데이터를 시각적으로 표현합니다. HTML, 템플릿 엔진, 프론트엔드 컴포넌트 등이 여기에 해당합니다.
Controller는 Model과 View 사이의 중재자 역할을 합니다. 사용자 입력을 받아 Model을 업데이트하고, 적절한 View를 선택하여 응답을 생성합니다. HTTP 요청 처리, 라우팅, 인증/인가 확인 등의 역할을 수행합니다. 이 세 컴포넌트의 분리로 각 영역을 독립적으로 개발하고 테스트할 수 있습니다.
현대 웹 프레임워크 대부분이 MVC 또는 그 변형(MVP, MVVM)을 채택합니다. Ruby on Rails가 "Convention over Configuration"으로 MVC를 대중화했고, Django는 MTV(Model-Template-View), ASP.NET MVC, Spring MVC 등이 널리 사용됩니다. React, Vue 같은 프론트엔드 프레임워크는 View 레이어에 집중하며, 전체 아키텍처에서 MVC의 일부를 담당합니다.
// Express.js + TypeScript로 구현한 MVC 패턴 예제
// ========== Model (models/User.ts) ==========
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
class UserModel {
private users: User[] = [];
private nextId = 1;
async findAll(): Promise<User[]> {
return this.users;
}
async findById(id: number): Promise<User | undefined> {
return this.users.find(user => user.id === id);
}
async create(data: Omit<User, 'id' | 'createdAt'>): Promise<User> {
const user: User = {
id: this.nextId++,
...data,
createdAt: new Date()
};
this.users.push(user);
return user;
}
async update(id: number, data: Partial<User>): Promise<User | null> {
const index = this.users.findIndex(u => u.id === id);
if (index === -1) return null;
this.users[index] = { ...this.users[index], ...data };
return this.users[index];
}
async delete(id: number): Promise<boolean> {
const index = this.users.findIndex(u => u.id === id);
if (index === -1) return false;
this.users.splice(index, 1);
return true;
}
}
// ========== Controller (controllers/UserController.ts) ==========
import { Request, Response } from 'express';
class UserController {
constructor(private userModel: UserModel) {}
async index(req: Request, res: Response) {
try {
const users = await this.userModel.findAll();
res.json({ success: true, data: users });
} catch (error) {
res.status(500).json({ success: false, error: 'Server error' });
}
}
async show(req: Request, res: Response) {
const id = parseInt(req.params.id);
const user = await this.userModel.findById(id);
if (!user) {
return res.status(404).json({ success: false, error: 'User not found' });
}
res.json({ success: true, data: user });
}
async create(req: Request, res: Response) {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ success: false, error: 'Name and email required' });
}
const user = await this.userModel.create({ name, email });
res.status(201).json({ success: true, data: user });
}
async update(req: Request, res: Response) {
const id = parseInt(req.params.id);
const user = await this.userModel.update(id, req.body);
if (!user) {
return res.status(404).json({ success: false, error: 'User not found' });
}
res.json({ success: true, data: user });
}
async delete(req: Request, res: Response) {
const id = parseInt(req.params.id);
const deleted = await this.userModel.delete(id);
if (!deleted) {
return res.status(404).json({ success: false, error: 'User not found' });
}
res.status(204).send();
}
}
// ========== Router (routes/users.ts) ==========
import { Router } from 'express';
const router = Router();
const userModel = new UserModel();
const userController = new UserController(userModel);
router.get('/', (req, res) => userController.index(req, res));
router.get('/:id', (req, res) => userController.show(req, res));
router.post('/', (req, res) => userController.create(req, res));
router.put('/:id', (req, res) => userController.update(req, res));
router.delete('/:id', (req, res) => userController.delete(req, res));
export default router;
💬 상황: 신규 프로젝트 아키텍처 설계 회의
원인: 초기 Ruby on Rails 기반 MVC에서 Controller에 비즈니스 로직이 집중됨
영향: "Fail Whale" 에러 페이지 빈번 노출, 대규모 이벤트 시 서비스 다운
해결: Scala 기반 마이크로서비스로 전면 재작성, Service 레이어 도입
교훈: MVC만으로는 대규모 트래픽 대응 어려움, Controller를 얇게 유지하고 Service 분리 필수
원인: 하나의 Model 클래스에 데이터 접근, 비즈니스 로직, 유효성 검사 모두 포함
영향: 수천 줄의 Model 파일, 작은 변경에도 전체 테스트 실패, 신규 개발자 온보딩 실패
해결: Repository 패턴으로 데이터 접근 분리, Domain 객체와 Entity 분리
교훈: 단일 책임 원칙(SRP) 준수, 각 컴포넌트의 역할 명확히 정의
Q1. MVC에서 Controller의 역할은?
Q2. "Fat Controller" 안티패턴의 문제점은?
Q3. Django의 MTV 패턴에서 View는 MVC의 어느 컴포넌트에 해당하는가?
이 페이지에 오류가 있거나 추가하고 싶은 내용이 있다면 알려주세요!