서비스를 처음 만들 때 가장 자연스러운 선택은 모든 코드를 하나의 프로세스로 실행하는 것이다. Monolithic Architecture는 이 직관을 그대로 반영한 설계로, 수십 년간 실제 프로덕션 환경을 지탱해 왔다. 작은 팀이 빠르게 제품을 검증해야 하는 초기 단계에서 이 구조가 여전히 가장 실용적인 이유가 있다.
Monolith의 내부는 보통 Layered Architecture로 구성된다. 각 Layer는 바로 아래 Layer에만 의존하며, 관심사를 수직으로 분리한다.
┌─────────────────────────────┐
│ Presentation Layer │ ← HTTP Controller, GraphQL Resolver
├─────────────────────────────┤
│ Application Layer │ ← Use Case, Service
├─────────────────────────────┤
│ Domain Layer │ ← Entity, Domain Service, Repository Interface
├─────────────────────────────┤
│ Infrastructure Layer │ ← DB, Cache, External API
└─────────────────────────────┘
Layered Architecture: 코드를 역할 단위로 수평 분리하는 패턴. 각 Layer는 인접한 Layer와만 통신하여 의존 방향을 단방향으로 유지한다.
모든 Layer가 동일한 프로세스 안에서 실행되므로 함수 호출로 통신한다. 네트워크 오버헤드가 없고 트랜잭션 관리가 단순하다.
| 상황 | 적합도 |
|---|---|
| 초기 스타트업, MVP | 높음 |
| 도메인 경계가 불분명한 시점 | 높음 |
| 팀 규모 1–5명 | 높음 |
| 트래픽 예측이 어려운 단계 | 보통 |
| 팀별 독립 배포가 필요한 시점 | 낮음 |
| 특정 컴포넌트만 Scale Out 필요 | 낮음 |
장점
단점
Scale Up: CPU·메모리 등 단일 서버의 사양을 높여 처리 용량을 늘리는 방식. 반대는 인스턴스 수를 늘리는 Scale Out.
Modular Monolith는 Monolith의 단점인 Tight Coupling을 해결하면서 단일 배포의 장점을 유지하는 중간 형태다. 각 도메인 모듈은 Public API(Interface)만 외부에 노출하고, 내부 구현을 캡슐화한다.
┌──────────────────────────────────────────┐
│ Single Process │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Order │ │ Inventory│ │ User │ │
│ │ Module │→ │ Module │ │ Module │ │
│ └──────────┘ └──────────┘ └────────┘ │
└──────────────────────────────────────────┘
모듈 간 의존은 Interface를 통해서만 허용하고, 직접 DB 테이블 참조를 금지하는 규칙을 코드 레벨에서 강제한다. MSA로 전환할 때 모듈 경계가 서비스 경계가 되므로 마이그레이션 비용이 줄어든다.
| 항목 | Monolith | MSA |
|---|---|---|
| 배포 단위 | 단일 | 서비스별 독립 |
| 통신 방식 | 함수 호출 | HTTP / Message Queue |
| 트랜잭션 | 단일 DB 트랜잭션 | Saga / 2PC 필요 |
| 운영 복잡도 | 낮음 | 높음 |
| 팀 자율성 | 낮음 | 높음 |
| 초기 개발 속도 | 빠름 | 느림 |
MSA(Microservices Architecture): 시스템을 독립적으로 배포 가능한 소규모 서비스로 분해하는 설계 방식. 서비스마다 별도 프로세스와 데이터 저장소를 가진다.
도메인 경계를 명확히 이해하기 전에 MSA를 선택하면 분산 시스템의 복잡성만 얻고 팀 자율성은 확보하지 못하는 결과로 이어진다. Monolith로 시작해 병목이 되는 도메인을 식별한 뒤 점진적으로 분리하는 전략이 현실적이다.