모듈러 모놀리스 (Modular Monolith) MSA 대안적 접근
핵심 인사이트 (3줄 요약)
- 본질: 모듈러 모놀리스(Modular Monolith)는 단일 프로세스와 단일 데이터베이스(Monolithic)로 배포되지만, 내부 코드는 바운디드 컨텍스트(Bounded Context)에 따라 엄격하게 분리된 모듈(Modular)로 구성되어 도메인 간 강결합을 원천적으로 차단하는 아키텍처 패턴이다.
- 가치: 마이크로서비스 아키텍처(MSA)가 수반하는 네트워크 지연(Latency), 분산 트랜잭션 관리, 배포 복잡성 등의 '분산 컴퓨팅의 저주'를 피하면서도, 코드의 독립성과 유지보수성이라는 MSA의 장점을 동시에 취할 수 있는 현실적이고 강력한 대안이다.
- 융합: 모듈러 모놀리스는 스타트업의 초기 아키텍처로 가장 적합하며, 완벽하게 모듈화된 코드는 향후 트래픽이 폭발하여 특정 모듈만 스케일 아웃이 필요해질 때 언제든 MSA로 안전하게 추출(Extract)할 수 있는 완벽한 징검다리(Stepping Stone) 역할을 한다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: 모놀리식 아키텍처는 모든 코드가 하나의 배포 단위로 묶여 있는 것을 말한다. 보통 모놀리스는 코드가 스파게티처럼 엉킨 '빅 볼 오브 머드(Big Ball of Mud)'를 연상시키지만, 모듈러 모놀리스는 코드가 1개의 덩어리로 배포될 뿐, 내부는 마치 완전히 다른 마이크로서비스들처럼 엄격한 인터페이스(API)를 통해서만 서로 통신하도록 캡슐화된(Encapsulated) 상태를 유지한다.
-
필요성: 최근 몇 년간 업계에는 "MSA가 무조건 정답이다"라는 과도한 유행이 불었다. 조직의 규모가 작고 도메인 경계가 명확하지 않은 상태에서 섣불리 MSA를 도입하면, 100ms면 끝날 로컬 메서드 호출이 수백 배 느린 네트워크 API 호출로 변하고, 장애 추적은 불가능해지며, 2PC와 사가 패턴(Saga Pattern) 관리 비용에 짓눌려 개발팀이 마비된다. 이를 극복하기 위해 물리적 분리(MSA) 대신 논리적 분리(Modular Monolith)가 실용적인 해답으로 재조명받게 되었다.
-
💡 비유: 큰 저택(모놀리스)에 방 구분 없이 다 같이 살면 사생활이 없어 엉망이 됩니다(Big Ball of Mud). 그렇다고 섣불리 집을 부수고 여러 채의 작은 집(MSA)을 지어 길을 뚫으면, 밥 한 번 같이 먹으려 해도 밖에 나가서 걸어가야 하죠. 모듈러 모놀리스는 큰 저택 지붕 아래에 완벽히 독립된 '아파트형 방(모듈)'들을 만들고, 내부 복도(인터페이스)를 통해서만 깔끔하게 교류하는 형태입니다.
-
등장 배경 및 발전 과정:
- Big Ball of Mud 시대: 과거의 모놀리스는 패키지만 나뉘어 있을 뿐, 주문 코드가 재고 DB를 직접 쿼리하는 등 무분별한 참조로 인해 유지보수가 불가능해졌다.
- MSA의 광풍과 환멸: 모놀리스의 대안으로 극단적인 MSA(Microservices Architecture)가 유행했으나, 분산 시스템의 복잡성을 감당하지 못한 많은 기업들이 프로젝트에 실패했다.
- Majestic Monolith의 귀환: Shopify, Stack Overflow 등 거대 테크 기업들이 오히려 모놀리스로 되돌아가거나 모놀리스를 고수하면서 엄청난 성과를 내고 있음을 발표하며, "잘 설계된 모놀리스(Majestic Monolith)" 즉 모듈러 모놀리스가 아키텍처의 정석으로 부활했다.
-
📢 섹션 요약 비유: 아직 요리법(도메인)이 픽스되지 않은 식당에서 무턱대고 주방 5개를 분리(MSA)해서 셰프들을 떨어뜨려 놓으면 배달 사고만 납니다. 일단 하나의 큰 주방(모놀리스) 안에서 구역(모듈)만 선으로 확실히 그어놓고 협업하는 것이 훨씬 똑똑한 방법입니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
구성 요소
| 요소명 | 역할 | 내부 동작 | 비유 |
|---|---|---|---|
| 모듈 (Module) | 바운디드 컨텍스트(Bounded Context)에 대응하는 논리적 단위 | 자신만의 비즈니스 로직, 데이터 접근 계층 소유 | 저택 내의 완벽히 독립된 개인 방 |
| 공개 인터페이스 (Public Interface) | 타 모듈이 접근할 수 있는 유일한 통로 | public 접근 제어자, Facade/Service 클래스 | 방문에 달린 인터폰 |
| 내부 구현체 (Internal Implementation) | 모듈 외부에서 절대 접근 불가한 로직과 엔티티 | package-private 또는 internal 접근 제어자 격리 | 방 안의 개인 서랍장 |
| 단일 데이터베이스 (Single DB) | 물리적으로 1개의 DB 사용 | (권장) 모듈별로 테이블 접근 권한 엄격히 분리 | 저택의 공용 냉고고 (단, 칸막이 필수) |
구조적 차이 비교: 스파게티 모놀리스 vs 모듈러 모놀리스 vs MSA
┌───────────────────────────────────────────────────────────────┐
│ 아키텍처 비교: 모듈 경계와 데이터 격리 (Isolation) │
├───────────────────────────────────────────────────────────────┤
│ │
│ [1] Big Ball of Mud (전통적 스파게티 모놀리스) │
│ Order 클래스가 Inventory 테이블을 직접 쿼리함! │
│ Order ────────▶ (직접 쿼리) ────────▶ Inventory_Table │
│ │
│ [2] Microservices Architecture (MSA) │
│ Order Svc ──▶ (네트워크 HTTP/gRPC) ──▶ Inventory Svc │
│ [Order DB] [Inventory DB] │
│ * 분산 트랜잭션, 네트워크 지연(Latency), 배포 복잡도 폭발! │
│ │
│ [3] Modular Monolith (모듈러 모놀리스) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ App Process (단일 배포 단위) │ │
│ │ │ │
│ │ ┌───────────────┐ ┌───────────────────┐ │ │
│ │ │ Order Module │ │ Inventory Module │ │ │
│ │ │ ├─OrderFacade │ ◀────▶ │ ├─InventoryFacade │ │ │
│ │ │ ├─OrderEntity │(메모리) │ ├─InventoryEntity │ │ │
│ │ └──────┬────────┘ 호출 └─────────┬─────────┘ │ │
│ └─────────┼───────────────────────────┼─────────────┘ │
│ ▼ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Single Database (DB 1개) │ │
│ │ [Order_Table] [Inventory_Table] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ▶ 장점: 네트워크 딜레이 0 (메모리 내 객체 호출), 로컬 트랜잭션(ACID) 보장,│
│ 동시에 모듈 간 코드는 완벽히 격리되어 유지보수 최상! │
└───────────────────────────────────────────────────────────────┘
[다이어그램 해설] 모듈러 모놀리스의 핵심은 "물리적 공유, 논리적 격리"다. 코드 베이스는 하나이고 JVM 같은 단일 프로세스에서 돌기 때문에 메서드 호출(In-memory Call)로 통신한다. 즉 네트워크 오버헤드가 제로(0)이고 분산 트랜잭션이 필요 없다. 하지만 코드를 작성할 때는 Order 모듈이 InventoryEntity나 InventoryTable을 절대로 직접 참조하지 못하게 막는다. 오직 InventoryFacade라는 공개된 인터페이스(API 역할)를 통해서만 데이터를 요청해야 한다. 이렇게 철저하게 선을 긋고 개발하면, 나중에 트래픽이 커져서 Inventory 모듈만 따로 떼어내 MSA로 만들 때 그냥 패키지를 들어내어 새 서버에 배포하고 메모리 호출을 HTTP 호출로 바꾸기만 하면 된다.
Ⅲ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 — 스타트업의 신규 서비스(Greenfield) 개발: 비즈니스 모델이 아직 완전히 검증되지 않아 요구사항이 하루가 다르게 변하는 초기 스타트업 환경. 엔지니어가 3명뿐인데 "확장성을 위해" 5개의 마이크로서비스로 쪼개어 쿠버네티스 위에 구축하려 하는 상황.
- 판단: 심각한 오버엔지니어링(Over-engineering)이다. 초기에는 도메인 경계(Bounded Context)가 명확하지 않아 서비스 간 인터페이스 변경이 빈번히 발생한다. MSA에서는 인터페이스 하나를 바꿀 때마다 여러 저장소를 수정하고 배포 일정을 맞추는 엄청난 비용이 든다.
- 해결책: 모듈러 모놀리스 아키텍처를 도입한다. 패키지나 멀티-모듈(Gradle/Maven) 구조로 바운디드 컨텍스트를 분리하고, 모든 모듈을 하나의 애플리케이션으로 묶어 빠르게 배포하며 비즈니스 로직 검증에 집중한다.
-
시나리오 — 모듈 간 데이터 격리 붕괴 방지: 팀원들이 바쁘다는 이유로, 결제(Payment) 모듈 로직에서 사용자(User) 모듈의
UserEntity를 직접import하여 DB를 강제 조인(Join)해서 사용해버리는 상황.- 판단: 논리적 격리가 무너지면 모듈러 모놀리스는 순식간에 과거의 Big Ball of Mud로 퇴보한다. 시스템적으로 이를 차단해야 한다.
- 해결책: 언어 레벨의 접근 제어자(Java의
package-private, C#의internal)를 철저히 활용하여 내부 엔티티의 외부 노출을 막는다. 또한 ArchUnit 같은 아키텍처 테스트 도구를 CI 파이프라인에 도입하여, "A 모듈은 B 모듈의 Entity 패키지를 참조할 수 없다"는 규칙을 코드로 강제하고 위반 시 빌드를 실패하게 만들어야 한다.
도입 체크리스트
- 비즈니스적: 현재 우리 조직이 다수의 마이크로서비스를 모니터링, 배포, 추적(Distributed Tracing)할 수 있는 인프라 역량과 데브옵스(DevOps) 인력을 갖추고 있는가? (없다면 모듈러 모놀리스가 유일한 정답이다.)
- 아키텍처적: 물리적으로는 단일 DB를 쓰더라도, 모듈 A가 모듈 B의 테이블에 직접 SQL 조인(Join)을 걸지 않도록 설계가 통제되고 있는가? (테이블 조인을 허용하는 순간 향후 MSA로의 분리는 영원히 불가능해진다.)
안티패턴
-
단일 테이블의 속성 혼재 (God Table): 코드 패키지는 모듈별로 예쁘게 나누어 놓았지만, 데이터베이스에는
User테이블 하나에 수십 개의 컬럼(인적사항, 주문 통계, 장바구니 정보 등)을 모두 때려 넣어 모든 모듈이 한 테이블에 동시 접근하게 만드는 패턴. 코드는 모듈화되었지만 데이터가 강결합되어 아키텍처가 붕괴된다. -
📢 섹션 요약 비유: 방 사이에 벽을 예쁘게 쳐놓았더라도, 바닥 밑으로 몰래 배관(DB 조인)을 마구잡이로 연결해 놓으면, 나중에 그 방 하나만 똑 떼어내서 이사(MSA 추출) 가려고 할 때 온 집안의 배관이 다 터져버립니다.
Ⅳ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | MSA (섣부른 도입 시) | 모듈러 모놀리스 | 개선 효과 |
|---|---|---|---|
| 정량 (성능) | 통신 시 네트워크 직렬화 지연 발생 (수 ms) | 메모리 내 직접 호출 (수 ns) | 마이크로서비스 대비 내부 통신 속도 수천 배 향상 |
| 정량 (운영) | N개의 CI/CD 파이프라인 유지보수 비용 | 단일 파이프라인, 단일 컨테이너 배포 | 배포 및 인프라 운영 비용 80% 이상 절감 |
| 정성 | 분산 트랜잭션(Saga) 설계로 비즈니스 로직 오염 | 로컬 트랜잭션 (ACID) 100% 보장 | 개발자는 비즈니스 핵심 로직 구현에만 전념 가능 |
MSA는 궁극적인 목표가 아니라, 조직과 트래픽이 모놀리스의 한계를 넘어섰을 때 적용하는 확장 수단일 뿐이다. 마틴 파울러(Martin Fowler)가 말했듯 "거의 모든 성공적인 마이크로서비스 아키텍처는 지나치게 커진 모놀리스에서 시작되었다." 기술사는 유행에 휩쓸려 무작정 MSA를 외치기보다, 조직의 역량과 도메인 성숙도를 냉정히 평가하고, 완벽한 도메인 분리(DDD)를 내재화한 모듈러 모놀리스를 통해 확장의 토대를 마련하는 실용주의적 리더십을 보여야 한다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 바운디드 컨텍스트 (Bounded Context) | 모듈러 모놀리스에서 패키지(모듈)를 나누는 가장 정확하고 핵심적인 기준선이다. |
| ArchUnit (아키텍처 테스트) | 코드 레벨에서 모듈 간의 불법적인 클래스 참조나 순환 참조를 강제로 막아 아키텍처가 부패하는 것을 방지하는 검증 도구다. |
| 스트랭글러 피그 (Strangler Fig) 패턴 | 향후 트래픽이 폭발할 때, 잘 분리된 모듈러 모놀리스의 특정 모듈 하나만을 떼어내어 점진적으로 MSA로 마이그레이션하는 표준 패턴이다. |
| 인메모리 이벤트 버스 (In-memory Event Bus) | 단일 프로세스 내에서도 모듈 간 강결합을 피하기 위해 Spring ApplicationEvent 같은 로컬 이벤트 기반 비동기 통신을 활용한다. |
| 콘웨이의 법칙 (Conway's Law) | 팀이 여러 개로 나뉘면 자연스럽게 모듈러 모놀리스조차 한계를 맞이하고, 각 팀이 독립적으로 배포할 수 있는 MSA로 넘어가게 되는 조직 구조적 원인이다. |
👶 어린이를 위한 3줄 비유 설명
- 커다란 피자를 만들 때 토핑들을 마구잡이로 섞어버리면 나중에 페퍼로니만 골라내기 힘들어요 (스파게티 모놀리스).
- 그렇다고 피자를 다 따로따로 구워서 포장하려면 굽는 데 시간도 오래 걸리고 배달 상자도 너무 많이 필요하죠 (과도한 MSA).
- 가장 좋은 방법은 한 판의 큰 피자를 굽되, 4등분으로 선을 긋고 치즈, 고구마, 불고기를 절대 섞이지 않게 예쁘게 올려서 한 번에 굽는 거예요. 이게 바로 '모듈러 모놀리스'랍니다!