마이크로서비스 아키텍처(MSA)를 도입할 때, 개발팀은 필연적으로 "어떻게 코드를 관리할 것인가?"라는 중요한 질문에 직면합니다. 이때 가장 대표적인 두 가지 전략이 바로 모노레포(Monorepo)와 폴리레포(Polyrepo)입니다.
1. 먼저, 우리 백엔드 구조 살펴보기
글을 시작하기에 앞서, 우리 백엔드 프로젝트의 폴더 구조를 간략하게 살펴보겠습니다.
backend/
├── booking/
│ ├── controller/
│ │ └── ReservationController.java // 예약 관련 API
│ ├── service/
│ │ └── ReservationServiceV1.java // 예약 핵심 로직
│ └── entity/
│ ├── Reservation.java
│ └── ReservationSlot.java
│
├── operationhour/
│ ├── controller/
│ │ └── OperationScheduleController.java // 운영시간 관련 API
│ └── service/
│ └── OperationScheduleService.java // 운영시간 핵심 로직
│
├── user/
│ └── service/
│ └── UserLoginService.java // 사용자 인증 로직
│
└── ... (core, common-dto 등 공통 모듈 및 다른 서비스들)
보시는 바와 같이, booking(예약), operationhour(운영시간), user(사용자) 등 기능적으로 분리된 여러 마이크로서비스들이 backend라는 단 하나의 Git 레포지토리 안에서 함께 관리되고 있습니다. 이것이 바로 이 글의 핵심 주제인 모노레포(Monorepo) 구조입니다.
2. 모노레포(Monorepo)란 무엇일까?
모노레포는 '단일(Mono)' 저장소(Repository)라는 의미로, 관련 있는 여러 프로젝트의 소스 코드를 단 하나의 레포지토리에서 관리하는 개발 전략입니다.
모노레포의 장점
- 손쉬운 코드 공유와 재사용: 여러 서비스에서 공통으로 사용되는 DTO나 유틸리티 클래스들을 별도의 라이브러리로 만들어 배포할 필요가 없습니다. 그냥 같은 프로젝트 내의 모듈을 임포트하듯 가져와 사용하면 되므로 코드 공유 비용이 매우 낮고 재사용성이 극대화됩니다.
- 원자적 커밋과 대규모 리팩토링: 여러 서비스에 걸친 변경 사항을 하나의 커밋으로 묶어 처리할 수 있습니다. 이는 시스템 전체의 일관성을 유지하는 데 결정적입니다. 예를 들어, 공통 DTO를 변경할 때, 해당 DTO를 사용하는 모든 서비스를 한 번에 수정하고 테스트하여 안정성을 확보할 수 있습니다.
- 통합된 의존성 관리: 프로젝트 내 모든 서비스가 동일한 버전의 Spring, JPA, 혹은 기타 라이브러리를 사용하도록 강제하기 쉽습니다. 이는 서비스별로 다른 라이브러리 버전을 사용하여 발생하는 미묘한 오류, 즉 "의존성 지옥(Dependency Hell)"을 원천적으로 방지합니다.
- 향상된 협업과 가시성: 모든 코드가 한곳에 있으므로, 개발자들은 다른 서비스의 코드를 쉽게 참고하고 자신의 변경사항이 다른 서비스에 미칠 영향을 쉽게 파악할 수 있어 더 나은 협업 환경을 만듭니다.
모노레포의 단점
- 레포지토리 크기 및 성능 저하: 프로젝트의 규모가 커질수록 Git 레포지토리의 전체 크기가 비대해져
clone,pull과 같은 기본 Git 작업 속도가 느려질 수 있습니다. - 복잡한 빌드 시스템 요구: 전체 코드를 매번 빌드하고 테스트하는 것은 비효율적이므로, 변경된 부분만 지능적으로 감지하여 빌드/테스트하는 고도화된 빌드 시스템(예: Nx, Bazel, Lerna)이 필요해집니다.
- 느슨한 코드 소유권: 서비스별로 코드 접근 권한을 설정하기가 상대적으로 까다로워, 특정 서비스의 코드를 아무나 수정할 수 있는 위험이 존재합니다.
3. 폴리레포(Polyrepo)란 무엇일까?
폴리레포는 모노레포와 반대되는 개념으로, 각 프로젝트나 마이크로서비스가 자신만의 독립적인 Git 레포지토리를 갖는 전략입니다. MSA의 "독립성" 철학에 가장 부합하는 전통적인 방식입니다.
폴리레포의 장점
- 명확한 코드 소유권과 팀 자율성: 각 레포지토리는 특정 팀이나 서비스가 온전히 소유합니다. 다른 팀과 협의 없이 독립적으로 기술 스택을 결정하고, 라이브러리를 업데이트하며, 배포 주기를 가져갈 수 있어 팀의 자율성이 극대화됩니다.
- 단순한 빌드 파이프라인: 각 레포지토리는 자신만의 독립적인 CI/CD 파이프라인을 가집니다. 설정이 간단하고 직관적이며, 다른 서비스의 상태와 관계없이 독립적으로 빌드 및 배포가 가능합니다.
- 작고 빠른 레포지토리: 각 레포지토리의 크기가 작아 Git 작업이 빠르고, 신규 입사자가 특정 서비스의 코드베이스를 이해하는 데 부담이 적습니다.
폴리레포의 단점
- 코드 공유의 어려움: 공통 코드를 사용하려면, 해당 코드를 별도의 라이브러리로 만들어 버전 관리를 하고, 패키지 저장소(Nexus, Artifactory 등)에 배포해야 합니다. 그리고 각 서비스에서는 이 라이브러리를 의존성으로 추가하여 사용해야 하므로 과정이 매우 번거롭습니다.
- 서비스 간 통합의 복잡성: 여러 서비스에 걸친 변경 작업을 하나의 트랜잭션처럼 처리하기가 거의 불가능합니다. 이는 서비스 간 버전 불일치와 통합 실패로 이어질 위험이 큽니다.
- 파편화된 개발 환경: 전사적으로 코드 스타일을 통일하거나 의존성 버전을 관리하기가 매우 어렵습니다. "A 서비스에서는 Jackson 라이브러리 v2.10을 쓰는데, B 서비스에서는 v2.12를 써서 발생하는 호환성 문제" 등이 발생하기 쉽습니다.
4. 현실 세계 시나리오로 비교하기
두 전략의 차이는 실제 개발 시나리오에서 극명하게 드러납니다.
시나리오 1: 모노레포가 유리한 경우 (여러 서비스에 걸친 API 변경)
상황:
user서비스의User엔티티에 '사용자 등급(grade)' 필드가 추가되었습니다. 이제booking서비스는 예약을 생성할 때 이 등급을 확인하여 할인을 적용해야 합니다.
- 모노레포 (현재 우리 구조)에서는?
개발자는 하나의 브랜치에서user엔티티와booking서비스 로직을 함께 수정합니다. 공통 DTO를 통해 '등급' 정보를 넘겨주도록 코드를 변경하면, IDE는 관련된 모든 코드의 컴파일 오류를 즉시 알려줍니다. 수정이 완료되면 이 모든 변경을 단일 커밋으로 제출하고, CI 시스템은 관련된 모든 서비스의 테스트를 함께 돌려 통합적인 안정성을 한 번에 검증합니다. 안전하고, 빠르고, 명확합니다. - 폴리레포였다면?
user레포지토리에서 엔티티 변경 후 API에 등급 정보를 추가하여 배포합니다.booking레포지토리에서 변경된userAPI를 호출하고 할인 로직을 추가하여 다시 배포합니다.
이 두 작업은 별개의 PR과 커밋으로 관리되며, 배포 순서가 꼬이거나 API 명세 전달에 실수가 있으면 전체 시스템이 장애를 일으킬 수 있습니다.
시나리오 2: 폴리레포가 유리한 경우 (독립적인 신규 서비스 개발)
상황: 기존 서비스들과는 별개로, 대용량 이벤트 데이터를 수집하고 분석하여 관리자에게 통계 대시보드를 제공하는
analytics서비스를 신규 개발하기로 했습니다. 이 서비스는 Python과 데이터 분석 라이브러리(Pandas, Scikit-learn)를 사용하는 것이 가장 효율적입니다.
- 모노레포 (현재 우리 구조)에서는?
기존 Java/Spring 기반의backend레포지토리에 Python 프로젝트를 통합하는 것은 매우 까다로운 일입니다. 전체 빌드 시스템을 Java와 Python을 모두 처리하도록 수정해야 하며, CI/CD 파이프라인의 복잡도가 기하급수적으로 증가합니다. 하나의 레포지토리에서 이질적인 기술 스택을 관리하는 것은 큰 부담이 됩니다. - 폴리레포였다면?
이것이 폴리레포가 빛을 발하는 순간입니다.analytics팀은 다른 서비스에 전혀 영향을 주지 않고backend-analytics라는 새로운 Git 레포지토리를 생성합니다. 그 안에서 자유롭게 Python, Poetry, Docker 등 자신들에게 가장 적합한 기술 스택을 구성하고 독립적인 배포 파이프라인을 구축합니다. 기존 서비스들과는 표준 API나 메시지 큐(Kafka)를 통해 필요한 최소한의 데이터만 주고받으면 그만입니다. 혁신과 실험이 다른 서비스의 안정성을 해치지 않습니다.
5. 최종 결론: 요약 및 선택의 근거
| 항목 | 모노레포 (Monorepo) | 폴리레포 (Polyrepo) |
|---|---|---|
| 코드 공유 | 매우 쉬움 (직접 참조) | 복잡함 (라이브러리 패키징/배포 필요) |
| 서비스 간 변경 | 원자적/안전함 (단일 커밋으로 동시 수정) | 복잡/위험함 (여러 레포에 걸친 동기화 필요) |
| 팀 자율성 | 낮음 (전체 규칙과 도구에 제약받음) | 높음 (팀/서비스별 기술 스택, 배포 주기 독립) |
| 기술 스택 다양성 | 어려움 (이질적인 스택 통합 시 복잡도 증가) | 쉬움 (서비스별로 최적의 기술 자유롭게 선택) |
결론적으로, 우리 서비스가 모노레포를 선택한 것은 매우 전략적인 결정입니다.
프로젝트의 booking, operationhour, user 서비스들은 비즈니스 로직 상 서로 매우 긴밀하게 연결되어 있습니다. 이처럼 서비스 간의 의존성이 높고, 함께 변경되어야 하는 경우가 잦으며, 전체 시스템의 일관성을 유지하는 것이 매우 중요한 프로젝트 환경에서는 폴리레포의 자율성이라는 장점보다 모노레포가 제공하는 안정적인 통합과 개발 속도 향상이라는 이점이 훨씬 큽니다.
팀의 자율성을 일부 희생하고 고도화된 빌드 시스템을 구축해야 하는 부담을 감수하는 대신, 서비스 간 정합성이 깨질 위험을 원천적으로 차단하고 더 빠른 속도로 기능을 개발하는 길을 택한 것입니다.
'BindProject' 카테고리의 다른 글
| 포인트 시스템 구축기 : 이벤트 발행 시스템을 위한 플러그인 아키텍처 구축기 (3) | 2025.07.29 |
|---|---|
| 포인트 시스템 구축기: Kafka와 RabbitMQ 과 문제 분석 (4) | 2025.07.28 |
| 스튜디오 예약 시스템의 설계와 구현 : 유연한 운영 시간 설계와 MSA의 시너지 (5) | 2025.07.27 |
| 클린코드를 위한 여정 : 빈혈증에 걸린 도메인 모델(Anemic Domain Model) 처리하기: USER_PROFILE (3) | 2025.07.26 |
| 클린코드를 위한 여정 : 가독성을 높힌 클린코드 리팩토링 : BandRoom 서비스... (2) | 2025.07.26 |