ADR-05: Polyrepo 리포지토리 전략
| 날짜 | 작성자 | 대상 리포 |
|---|---|---|
| 2024-12-17 | @KubrickCode | All |
Context
문제 상황
다중 서비스 플랫폼은 코드 구성 전략에 대한 근본적인 결정이 필요함. 이 결정은 다음에 영향을 미침:
- 개발 워크플로우: 서비스 경계를 넘나드는 개발 방식
- 릴리스 관리: 서비스 버저닝 및 배포 방식
- 팀 협업: 소유권과 책임 정의 방식
- 기술 진화: 기술 선택의 독립적 발전 가능성
리포지토리 전략 옵션
| 전략 | 설명 | 일반적 사용 사례 |
|---|---|---|
| Monorepo | 모든 서비스를 단일 리포지토리에 구성 | 긴밀하게 결합된 서비스, 동일 스택 |
| Polyrepo | 각 서비스를 독립 리포지토리로 분리 | 독립적 서비스, 다양한 스택 |
| Hybrid | 핵심은 Monorepo, 특정 컴포넌트만 분리 | 점진적 마이그레이션, 혼합 요구사항 |
핵심 결정 요소
- 기술 스택 다양성: 서비스별 다른 언어/프레임워크 사용
- 릴리스 독립성: 독립적인 배포 주기 필요성
- 팀 구조: 소유권 경계 및 접근 제어 요구사항
- 코드 공유 패턴: 공유 코드 변경 빈도
- 오픈소스 전략: 특정 컴포넌트 오픈소스화 계획
- 플랫폼 배포 요구사항: 마켓플레이스 및 레지스트리 제약
플랫폼 배포 제약
특정 배포 플랫폼은 독립 리포지토리를 요구하거나 강하게 선호함:
| 배포 대상 | 리포지토리 요구사항 | Monorepo 제약 |
|---|---|---|
| GitHub Action | action.yml이 리포지토리 루트에 위치 필수 | 불가능 - Marketplace 등록 안됨 |
| VSCode Extension | package.json이 루트, vsce 패키징 | 복잡한 워크스페이스 설정 필요 |
| Go Module | go.mod 경로가 import path가 됨 | 서브모듈 경로가 불편 (repo/pkg) |
| npm Package | 독립 패키지 관리 | 가능하나 워크스페이스 도구 필요 |
| Docker Hub | Dockerfile 컨텍스트가 루트 선호 | 멀티 컨텍스트 빌드로 복잡성 증가 |
이 제약은 생태계 확장(IDE 확장, CI 연동, CLI 도구) 계획 시 특히 중요함.
Decision
배포 단위와 기술 경계에 따라 리포지토리를 분리하는 Polyrepo 전략을 채택한다.
리포지토리 분리 기준:
- 배포 단위: 독립적으로 배포되는 서비스는 별도 리포지토리
- 기술 경계: 다른 주요 언어/프레임워크는 분리
- 소유권 경계: 명확한 팀 소유권은 리포지토리 경계와 일치
Options Considered
Option A: Polyrepo 전략 (선택됨)
각 서비스가 독립적인 라이프사이클을 가진 자체 리포지토리를 유지함.
장점:
- 독립적 릴리스 주기: 다른 서비스와 조율 없이 배포 가능
- 명확한 소유권: 리포지토리 경계 = 소유권 경계
- 기술 자유도: 각 서비스가 제약 없이 최적 스택 사용 가능
- 세분화된 접근 제어: 민감한 코드에 대한 리포지토리 수준 권한
- 오픈소스 활성화: 전체 코드베이스 노출 없이 특정 컴포넌트만 오픈소스화
- 집중된 CI/CD: 빠른 빌드, 영향받는 서비스만 테스트
- 단순화된 의존성: 크로스 언어 지원을 위한 빌드 시스템 복잡성 없음
단점:
- 크로스 리포지토리 변경: 여러 리포 PR은 조율 필요
- 의존성 동기화: 라이브러리 버전을 리포 간 추적 필요
- 코드 중복 위험: 공통 유틸리티가 중복될 수 있음
- 개발 환경 설정: 로컬 환경에 여러 리포지토리 클론 필요
Option B: Monorepo 전략
모든 서비스를 공유 도구와 함께 단일 리포지토리에 구성함.
장점:
- 원자적 변경: 단일 PR로 여러 서비스 수정 가능
- 코드 재사용: 공통 코드와 유틸리티 공유 용이
- 일관된 도구: 통합된 빌드 시스템과 CI/CD
- 단순화된 리팩토링: 대규모 변경 실행 용이
- 단일 진실의 원천: 모든 코드가 한 곳에
단점:
- 빌드 시스템 복잡성: 다중 언어 지원에 정교한 도구 필요 (Bazel, Nx)
- 스케일링 문제: 리포지토리 크기가 클론/체크아웃 시간에 영향
- 결합된 릴리스: 변경이 불필요한 재빌드를 유발할 수 있음
- 접근 제어 한계: 세분화된 권한 설정이 복잡
- 기술 고정: 단일 스택으로 표준화 압력
- 플랫폼 배포 제약: GitHub Actions Marketplace 등록 불가; VSCode 확장 복잡한 설정 필요
Option C: 하이브리드 접근
핵심 서비스는 Monorepo에, 특정 컴포넌트(라이브러리, 인프라)는 별도 리포에 구성함.
장점:
- 양쪽의 장점: 관련 서비스에 대해 Monorepo 이점 결합
- 선택적 분리: 특별한 요구사항이 있는 컴포넌트 격리
단점:
- 이중 복잡성: 두 패턴과 도구를 모두 유지 필요
- 경계 결정: 어디에 무엇이 속하는지 기준 불명확
- 마이그레이션 마찰: mono와 poly 섹션 간 코드 이동이 복잡
Consequences
Positive
독립적 릴리스 주기
- 서비스가 자체 일정에 따라 배포
- 릴리스 조율 오버헤드 없음
- 롤백 범위가 단일 서비스로 제한
명확한 소유권 경계
- 리포지토리 소유권 = 서비스 소유권
- 코드 리뷰 범위 명확 정의
- 책임 소재 명확
기술 유연성
- 각 서비스가 최적의 기술 사용
- 프레임워크/라이브러리 업그레이드가 연쇄되지 않음
- 언어별로 최적화된 빌드 시스템
접근 제어
- 민감한 코드(인프라, 시크릿) 격리
- 리포지토리별 기여자 권한
- 감사 추적 분리
오픈소스 가능성
- 라이브러리 컴포넌트를 독립적으로 배포 가능
- 별도 라이선스 가능
- 커뮤니티 기여 범위 제한
생태계 배포
- GitHub Actions를 Marketplace에 등록 가능 (루트
action.yml필요) - VSCode 확장을 복잡한 워크스페이스 설정 없이 배포 가능
- 각 도구/확장이 깔끔한 리포지토리 정체성 보유
- GitHub Actions를 Marketplace에 등록 가능 (루트
Negative
크로스 리포지토리 변경
- 다중 서비스 기능에 조율된 PR 필요
- API 계약 변경에 신중한 순서 지정 필요
- 완화책: 안정적인 API 계약, 버저닝 전략, 피처 플래그
의존성 동기화
- 리포지토리 간 공유 라이브러리 업데이트
- Breaking change 전달 필요
- 완화책: 자동화된 의존성 업데이트 (Renovate/Dependabot), 시맨틱 버저닝
코드 중복
- 유틸리티 코드가 중복될 수 있음
- 공통 패턴 재구현
- 완화책: 공유 코드를 전용 라이브러리 리포지토리로 추출
개발 환경
- 클론 및 구성할 리포지토리 다수
- E2E 테스트에 서비스 오케스트레이션 필요
- 완화책: 로컬 개발용 Docker Compose, 문서화
Technical Implications
| 측면 | 영향 |
|---|---|
| 의존성 관리 | Go 서비스는 Go modules, TypeScript는 npm |
| 버전 전략 | 라이브러리는 SemVer, 서비스는 독립 버저닝 |
| CI/CD | 리포지토리별 파이프라인, 전용 환경에서 통합 테스트 |
| 코드 공유 | 공유 라이브러리를 패키지로 배포 (Go modules, npm packages) |
| 로컬 개발 | 다중 서비스 설정을 위한 Docker Compose 또는 개발 스크립트 |
재검토 시점
- 크로스 리포지토리 변경이 개발 작업의 대부분이 될 때
- 팀 통합으로 공유 소유권이 더 실용적이 될 때
- 기술 스택이 단일 언어/프레임워크로 수렴할 때
- Monorepo 도구(Bazel, Nx)가 현재 규모에서 매력적이 될 때
