안녕하세요! AT SOPT 36기 OB 이진혁입니다.
오늘은 단순 이론적인 이야기보다 이론과 저의 생각을 풀어내는 글이 될 것 같아요. 그래서 감히 제가 이런 내용을 쓰는 것이 조금 걱정되기는 하지만, 단순히 저의 생각이니 정답이 아니라는 것을 인지하고 읽어주시면 더 좋을 것 같습니다!
제목에서도 알 수 있듯이 오늘은 MVC 패턴에 대해서 알아볼 것이다. 하지만 단순히 MVC 패턴을 파고드는 글이라기 보다는, MVC 패턴이 Frontend에서는 어떻게 적용이 될 수 있는지를 2주차 과제를 참고하면서 풀어내려고 한다.
MVC 패턴은 너무 유명한 아키텍쳐이기에, 개발을 시작하고부터 정말 많이 들어봤다. 하지만 어느 순간부터 생각이 들었다. 프론트엔드 개발에서는 이 MVC 패턴을 사용할 수 없는가? 백엔드에서 Java와 Spring으로 MVC를 사용하는 사례는 많이 봤지만, 이걸 프론트엔드에서 사용하는 것은 거의 보지 못했기 때문이다.
단순히 아키텍쳐이기에 적용하지 못하는 것은 아닐텐데, 왜 프론트엔드에서는 보기가 쉽지 않을까?
나는 이 궁금증을 해결하고자 이번 2주차 과제에 무작정 MVC 패턴을 도입해보고자 하였다. 그 전에 먼저 MVC 패턴에 대해서 간단하게 알아보자.
MVC 패턴
MVC 패턴은 이름 자체에 의미를 담고 있다. 각 Model, View, Controller 역할로 분리하여 개발할 수 있는 디자인 패턴이다.
- Model
- Model은 내부 비즈니스 로직을 처리하는 역할을 한다. Database와 연결되어 사용자와 상호작용(입출력)할 데이터를 다루고 데이터 변경 작업(CRUD)을 하나의 작업으로 묶은 트랜잭션을 다루는 일도 한다.
- 순수한 애플리케이션 데이터만 포함되어 있으며 사용자에게 데이터를 제공하는 로직은 포함되지 않는다.
- View
- View는 모델이 처리한 데이터를 사용자에게 시각적으로 제공한다. 예를 들어 HTML, CSS, JS를 사용하여 웹 브라우저가 출력할 UI를 만들어 제공하는 방식이 있다.
- View는 모델에 독립적이다. Model의 데이터에 엑세스하는 방법을 알고 있지만 이 데이터가 무엇을 의미하는지 또는 사용자가 이를 조작하기 위해 무엇을 할 수 있는지는 모른다. 즉, View는 사용자 인터페이스를 바꿔도 Model에 영향을 주지 않는다.
- Controller
- Controller는 전체 메커니즘의 흐름을 제어한다. Controller는 View와 Model 사이에 존재하며 View(또는 다른 외부 소스)에 의해 트리거된 Event를 수신하고 이러한 이벤트에 대한 적절한 응답을 하는 역할을 한다.
- 대부분의 경우 Controller의 응답은 Model에서 메서드를 호출하고 그 결과 데이터를 View에 전달한다.

더 요약하자면 Model은 애플리케이션의 핵심 데이터와 비즈니스 로직을 나타내고, View는 사용자에게 보여지는 UI 부분이며, Controller는 사용자 입력을 처리하고 애플리케이션의 흐름을 관리하는 것이다.
M-V-C 상호작용
MVC 패턴에서는 위에서 말한 이 3가지 요소가 상호작용을 한다.
사용자가 애플리케이션에서 작업을 수행하면, View는 사용자의 입력을 감지하고 Controller에 전달을 하게 된다. Controller는 사용자 입력을 처리하고 적절한 Model 기능을 호출하여 데이터를 검색, 수정 또는 저장하게 된다. 또한 Model은 데이터와 관련된 비즈니스 로직을 수행하고, 필요한 경우에는 DB와 상호 작용하고, 작업이 완료되면 결과를 Controller에 반환한다.
최종적으로 Controller는 Model의 결과를 받아 View에 전달하게 되고, View가 이 데이털르 사용해 사용자에게 보여지는 화면을 업데이트 하게 되는 것이다.
MVC를 왜?
그렇다면 이러한 MVC 패턴을 왜 사용하는 것일까?
위에서 말한 것처럼 MVC는 각 구성 요소의 역할이 명확하게 분리되어 있다. 그러다 보니 코드의 가독성과 유지 관리가 용이하고, 구성 요소간의 낮은 결합도로 인해 코드의 재사용성도 높아진다.
또한 동일한 Model을 여러 View에서 사용할 수 있으므로, 애플리케이션의 유연성 또한 향상이 된다. 물론 모든 기술에 장점이 있다면 동시에 단점이 반드시 존재하듯이 아래와 같은 한계점도 존재한다고 한다.
- Model과 View의 의존성을 완전히 분리시킬 수 없다.
- 컨트롤러의 비중이 높아져 부담이 커진다면 Massive-View-Controller 현상을 피할 수 없다.
이외에도 다양한 장점과 단점이 더 있지만 해당 글의 주제에 벗어나는 내용이므로 여기까지 하겠다.
그렇다면 Frontend에서 MVC는?
이제 다시 궁금해진다. 이 MVC 패턴을 Frontend에서는 적용할 수 없을까?
다시 한번 요약해서 MVC는 구성 요소의 역할을 명확히 하여, 전체 애플리케이션의 구조를 잡는 아키텍쳐일 뿐이다. 그렇기 때문에 당연히 Frontend에서 적용이 불가능할 이유가 없다.
이러한 장점을 직접 느끼기 위해 이번 2주차 과제를 MVC 패턴을 이용해 구현해보았다. 일부 코드로 구조를 같이 살펴보자.
이번 과제에서는 예시 data를 제공해주었다. 이는 결국 DB에 있는 데이터와 동일한 역할이라고 생각했고, 이를 DB 역할을 하는 data 폴더에 두었고 Model에서 이를 사용하도록 구현하였다.
Model
// data/todoData.js
export const todos = [
{
id: 1,
title: '과제하기',
completed: false,
priority: 1,
},
{
id: 2,
title: '밥 먹기',
completed: false,
priority: 2,
},
// ... 이후는 생략
이러한 DB 데이터를 기반으로 아래와 같은 Model을 구현했다.
// model/todoModel.js
import { todos } from '../data/todoData.js';
import { showModal } from '../view/todoView.js';
class TodoDTO {
constructor({ id, title, completed, priority }) {
this.id = id;
this.title = title;
this.completed = completed;
this.priority = priority;
}
}
const initTable = () => {
if (!localStorage.getItem('todos')) {
localStorage.setItem('todos', JSON.stringify(todos));
}
};
const getTodos = () => {
const todos = localStorage.getItem('todos');
return todos ? JSON.parse(todos).map((todo) => new TodoDTO(todo)) : [];
};
const setTodos = (updateTodos) => {
localStorage.setItem('todos', JSON.stringify(updateTodos));
};
const addTodo = (newTodo) => {
const storedTodos = getTodos();
const updatedTodos = [...storedTodos, new TodoDTO(newTodo)];
setTodos(updatedTodos);
};
const deleteTodo = (id) => {
const storedTodos = getTodos();
const updatedTodos = storedTodos.filter((todo) => todo.id !== id);
setTodos(updatedTodos);
};
// ...
이때 마치 Todo가 추가되는 것이 localStorage(DB역할)에 데이터를 주고 받는 요소라고 생각하여, Todo를 DTO(Data Transfer Object)로 두어 사용하도록 해보았다.
또한 이 DB에 저장하고, 가져오며 관련된 비즈니스 로직을 함수를 통해 Model에서 책임지도록 구현했다.
Controller
Controller에서는 이러한 Model의 요소를 활용해 View에 전달을 해줄 수 있도록 handle 함수들을 정의해보았다.
// controller/todoController.js
const handleAddTodo = () => {
const input = document.querySelector('input[type="text"]');
const priority = document.querySelector('#add_select');
if (input.value.trim() === '' || !priority.value) {
alert('할 일과 중요도 중에 빈 값이 있습니다!');
return;
}
const newTodo = new TodoDTO({
id: Date.now(),
title: input.value,
completed: false,
priority: parseInt(priority.value, 10),
});
addTodo(newTodo);
resetInput();
renderTable(getTodos());
};
const handleDeleteSelectedTodos = () => {
const checkboxes = document.querySelectorAll(
'input[type="checkbox"]:checked'
);
checkboxes.forEach((checkbox) => {
const id = parseInt(checkbox.dataset.id, 10);
deleteTodo(id);
});
renderTable(getTodos());
};
// ...
View
마지막으로 View이다. 정말 의미 그대로 사용자에게 UI를 띄워주는 역할을 하는 render 함수 등만 위치하였다.
// view/todoView.js
const renderTable = (todos) => {
const tableBody = document.querySelector('tbody');
tableBody.innerHTML = '';
todos.forEach((todo) => {
const row = document.createElement('tr');
row.setAttribute('draggable', true);
row.dataset.id = todo.id;
row.innerHTML = `
<td><input type="checkbox" data-id="${todo.id}"></td>
<td>${todo.priority}</td>
<td>${todo.completed ? '✅' : '❌'}</td>
<td>${todo.title}</td>
`;
// 드래그 앤 드랍 이벤트 리스너
row.addEventListener('dragstart', (e) => handleDragStart(e, todo.id));
row.addEventListener('dragover', handleDragOver);
row.addEventListener('drop', (e) => handleDrop(e, todo.id));
tableBody.appendChild(row);
});
updateCheckboxListeners();
};
//...
이렇게 하니 확실한 장점은 보이기 시작했다. 단순히 JS 로직을 의미 없이 나열하는 것이 아니라, UI를 책임지는 로직과 데이터를 처리하는 로직 등이 구분 되니 코드의 가독성이 정말 좋아졌다.
만약 Module 식으로 함수 등만 나눈다고 생각했다면, DOM 관련 로직, Event 관련 로직, 데이터를 처리하고 UI로 띄워주는 로직 등 여러 역할을 가지는 로직 들이 섞여 정말 보기가 힘들었을 것이다.
🤔 React를 사용하면서도 사용이 가능할까?
물론 현재 과제는 바닐라 JS로 구현하는 과제였기 때문에 React의 패러다임 등을 생각할 필요가 없었다. 그렇기 때문에 전체 구조를 내가 원하는대로 짤 수 있었고, 그래서 MVC으로 아키텍쳐를 접근하기에 더 편했을 수도 있다고 생각한다.
또한 Java로 백엔드 개발을 할 때 MVC와 객체지향 프로그래밍을 같이 사용할텐데, 나는 객체지향을 사용하지도 않았다.
(물론 JS도 class 개념이 있긴 하지만 말이다.)
어쨌든 MVC도 아키텍쳐다. 단순히 애플리케이션의 구조를 짜는 패턴이기 때문에 프론트엔드에서도 문제 없이 적용이 가능한 것이라고 생각한다. 하지만 프론트엔드에서는 react를 거의 필수로 사용하는데, 이 react는 View에 초점을 맞춘 라이브러리이기 때문에 이러한 라이브러리를 사용할 때 전체 아키텍쳐를 MVC로 적용하는 것이 쉽지 않은 것이 아닐까? 라는 생각도 든다.
또한 React가 Flux 패턴을 사용해서 만들어진 컴포넌트 기반의 아키텍쳐를 이미 사용하기 때문에 react로 개발을 하면서 MVC를 적용하는 예제가 많이 없는 것이 아닐까라는 생각 또한 하게 된다. React를 쓰면서도 MVC를 쓰는 예시를 구글에서 찾을 수는 있었지만 이게 정말 좋은 패턴인가?에 대한 답은 아직 내가 깨달을 만큼 지식이 충분하지 않은 것 같다.

아무튼! 바닐라 JS에서는 MVC를 적용하기에 무리가 없었고 이를 활용하니 확실한 가독성과 책임 분리라는 다양한 장점을 확인할 수 있었다. 아직 많이 부족하지만 MVC를 왜 쓰는지에 대한 정말 작은 감은 잡히는 것 같다.
어렵다. MVC. 코딩. 후론트.
물론 여기서.. 다시 언급하자면..!
저도 MVC 패턴을 많이 사용해보지 않았기에 이 코드가 정말 MVC 패턴을 잘 구현한 것인지.. 확신이 들지 않습니다. 그래서 MVC 패턴을 공부하며 따라해보려고 노력했다..! 정도로 생각해주시면 너무 좋을 것 같아요!

[ 참고 자료 ]
'2주차' 카테고리의 다른 글
| 자바스크립트 ID로 값 찾기 vs class 로 찾기 (0) | 2025.04.26 |
|---|---|
| 프로미스 (1) | 2025.04.25 |
| 어떤 개발환경을 구축해야 할까? (0) | 2025.04.25 |
| React를 사용하는 이유 ? (React vs Vue vs Angular) (0) | 2025.04.25 |
| 번들러 (0) | 2025.04.25 |