🟨 1-15. 모듈화 + 객체지향 설계로 프로젝트 구조 설계하기 — 폴더 구조부터 설계 철학까지
1. 왜 설계가 중요한가
프론트엔드든 백엔드든, 규모가 커질수록 코드의 “양”보다 “구조”가 중요해진다.
잘 짜인 설계는 버그보다 이해하지 못한 코드를 줄이는 데 목적이 있다.
코드의 품질은 “동작”이 아니라 “이해도”로 결정된다.
객체지향과 모듈화를 결합하면,
한눈에 ‘이 코드가 어디에 속하는지’ 파악할 수 있는 구조가 된다.
2. 코드 설계의 3가지 원칙
아무리 복잡한 프로젝트라도 다음 세 가지 원칙만 지키면 안정적인 구조를 만들 수 있다.
- 단일 책임 원칙 (SRP) — 하나의 파일/클래스는 한 가지 역할만 수행
- 의존성 역전 원칙 (DIP) — 상위 모듈은 하위 구현에 의존하지 말 것
- 모듈 독립성 원칙 (Modularity) — 각 모듈은 최소한의 연결로 동작해야 함
이 세 가지는 단순히 철학이 아니라,
실제 유지보수 비용을 줄이는 가장 현실적인 설계 전략이다.
3. 기본 폴더 구조 설계
아래는 자바스크립트 기반의 중형 규모 프로젝트에서 가장 자주 쓰이는 구조 예시다.
src/
┣ core/ # 공통 기반 (Config, Error, Utils)
┣ models/ # 데이터 구조 정의 (클래스 중심)
┣ services/ # 비즈니스 로직 (API, Auth, Storage 등)
┣ components/ # UI 컴포넌트 (프론트엔드일 경우)
┣ controllers/ # 앱의 주요 흐름 제어
┣ index.js # 진입점 (app 초기화)
각 디렉터리는 “하나의 역할”에 집중한다.
예를 들어 services에는 API나 인증 로직,
models에는 데이터를 구조화한 클래스가 들어간다.
4. 예시 — 객체지향 기반의 유저 관리 시스템
실제 예시를 만들어보자.
1️⃣ models/User.js
export default class User {
#email;
#name;
constructor(email, name) {
this.#email = email;
this.#name = name;
}
get email() {
return this.#email;
}
get name() {
return this.#name;
}
rename(newName) {
if (newName.length < 2) throw new Error("이름은 두 글자 이상이어야 합니다.");
this.#name = newName;
}
}
→ 사용자 정보를 관리하는 모델 (데이터 구조 중심)
2️⃣ services/UserService.js
import User from "../models/User.js";
export default class UserService {
#users = [];
addUser(email, name) {
const user = new User(email, name);
this.#users.push(user);
return user;
}
findByEmail(email) {
return this.#users.find(u => u.email === email);
}
getAll() {
return [...this.#users];
}
}
→ User 모델을 활용해 비즈니스 로직을 수행하는 서비스 계층
3️⃣ controllers/UserController.js
import UserService from "../services/UserService.js";
export default class UserController {
#service;
constructor() {
this.#service = new UserService();
}
registerUser(email, name) {
const user = this.#service.addUser(email, name);
console.log(`✅ 사용자 등록 완료: ${user.name} (${user.email})`);
}
listUsers() {
console.table(this.#service.getAll());
}
}
→ 외부 요청(예: API, 버튼 클릭 등)을 받아 서비스 로직을 호출하는 역할
4️⃣ index.js
import UserController from "./controllers/UserController.js";
const controller = new UserController();
controller.registerUser("dev@example.com", "기범");
controller.registerUser("test@naver.com", "민수");
controller.listUsers();
실행 결과:
✅ 사용자 등록 완료: 기범 (dev@example.com)
✅ 사용자 등록 완료: 민수 (test@naver.com)
┌─────────┬────────────────────┬──────┐
│ (index) │ email │ name │
├─────────┼────────────────────┼──────┤
│ 0 │ 'dev@example.com' │ '기범' │
│ 1 │ 'test@naver.com' │ '민수' │
└─────────┴────────────────────┴──────┘
5. 이런 구조가 좋은 이유
장점 설명
| 유지보수 용이 | 각 기능이 독립되어 수정 영향이 적음 |
| 테스트 용이 | 서비스 단위로 단위 테스트 가능 |
| 확장성 | 새로운 기능 추가 시 기존 코드 건드릴 필요 없음 |
| 협업 효율 | 파일 구조만 봐도 역할이 바로 파악됨 |
이 구조는 프레임워크가 없는 순수 자바스크립트 환경에서도 그대로 적용 가능하며,
React, Vue, Express, Next.js 같은 프레임워크에서도 거의 동일한 철학을 유지한다.
6. 서비스 확장 예시
만약 로그인 기능이 추가된다면?
새로운 파일을 하나만 추가하면 된다.
services/AuthService.js
export default class AuthService {
login(email, password) {
// 실제로는 서버 API 호출
if (email && password) {
console.log(`🔐 ${email} 로그인 성공`);
} else {
console.log("❌ 로그인 실패");
}
}
}
→ 기존 구조를 건드리지 않고 자연스럽게 확장된다.
이게 좋은 설계의 핵심 — 개방 폐쇄 원칙(Open/Closed Principle) 이다.
7. 프로젝트 수준에서의 모듈 의존 관계
의존 관계를 시각화하면 다음과 같다.
[Controller] → [Service] → [Model]
↑
│ (UI / API 요청)
│
[View or Route Layer]
이 방향은 “한쪽으로만” 흐른다.
상위 레벨(Controller)은 하위 레벨(Service)의 구현 세부사항을 직접 알 필요가 없다.
→ 이렇게 하면 변경이 하위에만 국한되어 안전하다.
8. 모듈 간 의존성을 줄이는 전략
- 의존 주입(Dependency Injection) 으로 테스트성 확보→ 나중에 테스트 시 mock 객체를 주입할 수 있음.
- class UserController { constructor(service = new UserService()) { this.service = service; } }
- 단일 진입점(Index) 을 만들어 불필요한 import 중복 제거→ import { UserService } from "../services" 형태로 간결하게 관리
- export { default as UserService } from "./UserService.js"; export { default as AuthService } from "./AuthService.js";
9. 실무에서 자주 쓰는 폴더 구조 패턴
패턴 특징
| Layered Architecture | Controller → Service → Model 계층형 구조 |
| Feature-Based Architecture | 기능(Feature) 단위로 폴더 묶음 (users/, auth/, posts/) |
| FSD (Feature-Sliced Design) | 기능 + 레이어를 결합한 고급 구조 (React 프로젝트에 적합) |
예를 들어 FSD 기반 구조는 다음과 같다 👇
src/
┣ entities/
┃ ┗ user/
┣ features/
┃ ┗ login/
┣ pages/
┃ ┗ home/
┣ shared/
┃ ┗ ui/
→ 규모가 커질수록 “폴더를 책임 단위로 자른다”는 사고가 중요하다.
10. 마무리
자바스크립트는 동적 언어지만,
객체지향 + 모듈 설계를 적절히 결합하면 충분히 구조적이고 유지보수 가능한 시스템을 만들 수 있다.
정리하자면:
- 모듈은 기능을 나누는 단위
- 클래스는 로직을 추상화하는 도구
- 서비스는 비즈니스 규칙의 중심
- 컨트롤러는 흐름 제어
- 구조는 커질수록 “명확한 역할 구분”이 품질을 좌우한다
잘 설계된 폴더 구조는 개발자 간의 공통 언어다.
“이 코드가 어디에 속하는가?”를 한눈에 알 수 있는 설계가 최고의 유지보수다.
다음 편에서는
1-16. ES6 모듈 + 비동기 + 클래스 기반으로 실제 미니 프로젝트 만들기 (User CRUD 실습)
을 통해 지금까지 배운 개념을 “하나의 완전한 기능”으로 구현해보자.
코드 중심으로 CRUD 전체 흐름을 직접 만들어볼까?