22. 모노레포 운영 가이드

0. 먼저 알고 가기 (30초 요약)

  • 모노레포는 관리 포인트를 하나로 묶되, 책임을 패키지 단위로 나눠야 합니다.
  • 빌드/테스트 캐시와 의존성 그래프를 기준으로 영향 범위를 계산하세요.
  • 팀별 권한 경계가 흐려지면 변경 충돌이 급증합니다.

세 줄 요약 비유: 모노레포는 한 건물에 여러 사무실(패키지)이 함께 입주한 구조입니다. 출입 통제(boundary), 공용 설비 관리(공유 패키지), 입주사 대표(owner)가 명확하지 않으면 사고가 잦아집니다.

초심자용 한눈에 보기

모노레포는 프로젝트가 커져도 소유권과 변경 범위를 잃지 않게 해주는 운영 구조입니다.

일상 비유: 모노레포는 한 건물에 여러 회사가 입주한 오피스 빌딩과 같습니다. 같은 주소(저장소)를 공유하지만, 각 사무실(패키지)에는 입주사(owner)와 출입 규칙(boundary)이 분명히 있어야 충돌이 줄어듭니다.

핵심 용어 빠르게 정리

용어 쉬운 뜻
package 기능/도메인을 담은 개별 배포 단위
workspace 여러 패키지를 한 번에 관리하는 루트 설정
소유권 어떤 팀이 어떤 패키지를 책임지는지 범위
태스크 그래프 어떤 작업이 먼저/나중에 실행되어야 하는 의존성 맵
캐시 히트 이전 결과를 재사용해 빌드/테스트 시간을 줄인 상태

모노레포 구조 한눈에 보기

왜 중요한가: 패키지 간 책임 흐름을 시각화하면 "어디까지가 우리 팀 범위인지"가 분명해집니다.

flowchart TD
  subgraph Apps["Apps 계층"]
    A1[웹 앱]
    A2[관리자 앱]
    A3[모바일 웹]
  end
  subgraph Features["Feature 패키지"]
    F1[checkout]
    F2[profile]
    F3[search]
  end
  subgraph Shared["Shared 패키지"]
    S1[ui-kit]
    S2[utils]
    S3[api-client]
    S4[config]
  end

  A1 --> F1
  A1 --> F2
  A2 --> F2
  A2 --> F3
  A3 --> F1
  F1 --> S1
  F1 --> S3
  F2 --> S1
  F2 --> S2
  F3 --> S3
  S1 --> S4
  S2 --> S4
  S3 --> S4

  classDef app fill:#e3f2fd,stroke:#1976d2;
  classDef feat fill:#fff3e0,stroke:#f57c00;
  classDef shared fill:#e8f5e9,stroke:#388e3c;
  class A1,A2,A3 app;
  class F1,F2,F3 feat;
  class S1,S2,S3,S4 shared;

화살표는 의존 방향을 뜻하고, 반드시 위에서 아래로만 흘러야 합니다. 역방향 의존은 boundary lint로 차단합니다.

분류 아키텍처 & 운영 상태 Stable
연관 가이드 04. 아키텍처, 07. 테스팅, 11. CI/CD, 16. 코드리뷰 도구 원칙 벤더 중립
핵심 테마 package boundary, ownership, task graph, cache, versioning, release, governance Update 최신 기준

모노레포 표준은 특정 빌드 플랫폼이나 원격 캐시 제품이 아니라 여러 앱과 패키지를 한 저장소에서 안전하고 빠르게 변경하기 위한 ownership, 경계, 자동화 규칙입니다.


추천 항목 (실무 우선순위)

  • 시작 추천: package별 소유권과 리뷰 책임자를 먼저 고정해 리뷰 충돌을 줄입니다.
  • 안정 추천: 의존성 그래프 기반 빌드 순서를 고정하고 병목이 큰 패키지부터 캐시 전략을 개선하세요.
  • 운영 추천: 브랜치 정책과 공용 CI 캐시 정책을 정기 점검해 비용/시간 낭비를 줄입니다.

추천 항목 고도화 체크

  • 첫 적용 — package owner, task graph, affected build/test 중 하나를 실제 PR이나 운영 이슈에 붙이고, 변경 전 기준을 먼저 적는다.
  • 증거 정리 — affected graph, boundary lint, release plan, package owner 승인를 같은 작업 기록에 남긴다.
  • 재점검 — 불필요한 rebuild, cross-package breakage, owner 누락가 나아졌는지 30일 안에 확인하고 기준을 유지, 수정, 폐기 중 하나로 판정한다.

추천 항목 실행 기록 템플릿

  • 작업 : package owner, task graph, affected build/test 적용 범위를 어느 화면, 패키지, 문서에 둘지 적는다.
  • 증거 : affected graph, boundary lint, release plan, package owner 승인 중 실제로 남긴 항목만 링크한다.
  • 판정 : 유지/수정/폐기 중 하나와 이유를 한 문장으로 남긴다.
  • 다음 점검 : 불필요한 rebuild, cross-package breakage, owner 누락를 다시 볼 날짜와 담당자를 지정한다.

문서 책임 범위

이 문서가 결정하는 것 단일 출처로 따르는 문서
package boundary, task graph, cache, ownership 04. 아키텍처, 11. CI/CD
shared package release와 migration note 14. 배포, 16. 코드리뷰
MFE와 monorepo의 책임 경계 21. 마이크로 프론트엔드
AI가 제안한 package split 검증 책임 18. AI 개발 워크플로우

0. 모든 프론트엔드 그룹 공통 Baseline

영역 공통 기준 검증 방법
Ownership app/package별 owner와 reviewer를 명시 CODEOWNERS 또는 동등한 규칙
Boundary public API와 internal module을 분리 import boundary lint
Task graph build/test/lint 의존 관계를 선언 affected command
Cache deterministic output만 캐시 cache key와 output 검증
Versioning 독립/고정 버전 전략을 문서화 release note
Release 변경 패키지와 영향 앱을 자동 계산 change report
Governance 새 패키지 생성, dependency 추가, breaking change 기준 RFC/ADR

0.0 모노레포 운영 실행 흐름

왜 중요한가: 변경 한 줄이 어떤 패키지·앱에 영향을 주는지 그래프로 추적해야 "혹시 모르는데"라는 막연한 두려움이 사라집니다.

flowchart TD
  A[패키지 변경 요구] --> B[Ownership 및 공개 API 재확인]
  B --> C[Boundary/Import 규칙 적용]
  C --> D[의존 그래프 영향 분석]
  D --> E[affected 기반 lint/test/type/build]
  E --> F{캐시 재현성}
  F -->|의심| G[cache key 정합성 점검]
  F -->|정상| H[변경 범위 배포 계획 수립]
  G --> F
  H --> I[Release Notes + Migration Note]
  I --> J[Consumer smoke & 관측 지표 기록]

Affected 실행 결정 트리

일상 비유: 도시락 공장에서 반찬 한 종류만 바꿨다고 모든 도시락을 다시 만들 필요는 없습니다. 그 반찬을 쓰는 메뉴(소비 앱)만 다시 만들면 됩니다.

flowchart TD
  Start[PR 변경 감지] --> Diff{변경된 파일 종류}
  Diff -->|루트 lockfile / config| FullRun[전체 그래프 재실행]
  Diff -->|패키지 source / test| Graph[의존 그래프 계산]
  Diff -->|문서 only| DocsOnly[docs CI만 실행]

  Graph --> Calc[직접 변경 + 역방향 소비자 식별]
  Calc --> CacheCheck{캐시 키 일치}
  CacheCheck -->|HIT| Replay[캐시 결과 재사용]
  CacheCheck -->|MISS| Exec[lint/test/build 실행]
  Exec --> Store[결과 캐시 저장]
  Store --> Report[skipped/executed 리포트]
  Replay --> Report
  FullRun --> Report
  DocsOnly --> Report

캐시 키 결정 트리

캐시 키는 "지문"과 같습니다. 입력이 같으면 같은 출력을 보장해야 하므로 시간·랜덤·외부 네트워크 같은 변동 요소는 키에서 빠져야 합니다.

flowchart TD
  Input[작업 입력 수집] --> Source[소스 파일 hash]
  Input --> Config[설정/툴 버전 hash]
  Input --> Deps[lockfile / peer 버전]
  Input --> Env[env schema / 빌드 flag]
  Source --> Key[캐시 키 생성]
  Config --> Key
  Deps --> Key
  Env --> Key
  Key --> Det{deterministic 여부}
  Det -->|예| Save[원격/로컬 캐시 저장]
  Det -->|아니오| Skip[캐시 저장 보류 + 알람]
  Save --> Reuse[다음 실행 재사용 가능]
  Skip --> Diag["원인 추적: 시간/랜덤/네트워크 의존?"]
  Diag --> Fix[deterministic 입력으로 정규화]
  Fix --> Key

0.1 교차 검증 매트릭스

권고 1차 출처 실행 증거 운영 증거 철회 조건
package boundary는 lint와 graph로 강제한다 workspace/package manager 공식 문서 boundary lint, circular dependency check cross-package defect, review delay 저장소 규모가 작아 규칙 비용이 더 큰 경우
affected execution은 CI 기본 경로로 둔다 빌드 도구 task graph 문서 changed package test, cache key audit CI duration, skipped failure rate graph 정확도를 신뢰할 수 없을 때
캐시는 deterministic output만 저장한다 빌드 도구 cache 문서 cache replay test, output hash diff flaky build, cache miss ratio 외부 네트워크/시간 의존 output을 제거할 수 없을 때
shared package release는 migration note와 owner를 요구한다 semver, package release 표준 changeset/release note validation breaking change incident app-only release가 더 단순하고 안전할 때

0.2 운영 게이트

Gate Evidence Owner Rollback
패키지 생성 owner, public API, dependency policy, RFC/ADR 링크 Package owner 패키지 생성 보류 또는 app 내부 구현 유지
Boundary 검증 import boundary lint, circular dependency report Architecture owner 잘못된 import revert 또는 public API 보강
Affected 실행 changed package report, cache key audit, skipped task 목록 CI owner full CI 실행으로 회귀
Shared release changeset, migration note, 영향 앱 테스트 Release owner 이전 버전 pin 또는 app-only rollback

1. 패키지 분류

왜 중요한가: 같은 모노레포 안에서도 "패키지의 정체성"이 달라 책임 범위와 릴리스 주기가 달라집니다. 분류가 흐리면 utils 패키지가 모든 변경의 진앙이 됩니다.

유형 예시 기준
App 사용자에게 배포되는 애플리케이션 독립 build/deploy 가능
Feature package 특정 도메인 기능 app 간 공유 전 owner 지정
UI package 디자인 시스템/컴포넌트 접근성/테마/RTL 기준 포함
Utility package 순수 함수, 타입, 설정 side effect 최소화
Config package lint, tsconfig, test preset 변경 영향 범위 명확화

공유 패키지는 편의가 아니라 계약입니다. owner, 버전, 테스트, breaking change 정책이 없으면 공유하지 않습니다.

1.1 새 패키지 생성 의사결정

일상 비유: 새 가게를 같은 상가에 낼지(패키지 신설), 기존 가게의 메뉴로 추가할지(앱 내부 모듈)는 손님 동선과 매출 영향에 달려 있습니다.

flowchart TD
  Need[공유하고 싶은 코드 발견] --> Q1{2개 이상 app에서 사용?}
  Q1 -->|아니오| InApp[해당 app 내부 모듈로 유지]
  Q1 -->|예| Q2{소유 팀이 명확한가?}
  Q2 -->|아니오| Defer[owner 합의 전까지 보류]
  Q2 -->|예| Q3{Breaking change 정책 합의?}
  Q3 -->|아니오| Defer
  Q3 -->|예| Q4{독립 테스트 가능?}
  Q4 -->|아니오| Refactor[테스트 가능한 단위로 분해]
  Q4 -->|예| Create[새 패키지 생성 + RFC/ADR 작성]
  Refactor --> Q4
  Create --> Public[public API 명시]
  Public --> Release[릴리스 정책 등록]

2. Boundary 규칙

왜 중요한가: boundary 없이 자라는 모노레포는 "어디서 무엇을 import 했는지" 추적이 불가능해지고, 패키지 하나의 변경이 예측 못 한 곳까지 번집니다.

규칙 기준
Public API 패키지 root export만 외부 사용
Internal import 다른 패키지의 src/internal 직접 import 금지
Layering app -> feature -> shared 방향 유지
Circular dependency CI에서 차단
Side effect 초기화 코드는 명시적 함수로 분리

boundary 위반은 코드 리뷰에서 잡기보다 lint/graph 검사로 자동 차단합니다.

2.1 허용 vs 금지 import 패턴

flowchart LR
  subgraph OK["허용"]
    A1["@scope/feature-x"] -->|"index.ts (public API)"| A2["@scope/ui-kit"]
    A2 -->|"index.ts"| A3["@scope/utils"]
  end
  subgraph NG["금지"]
    B1["@scope/feature-x"] -.->|"src/internal/private.ts"| B2["@scope/ui-kit"]
    B3["@scope/utils"] -.->|"역방향"| B4["@scope/feature-y"]
    B5["@scope/feature-a"] -.->|"feature → feature"| B6["@scope/feature-b"]
  end
  classDef ok fill:#e8f5e9,stroke:#2e7d32;
  classDef ng fill:#ffebee,stroke:#c62828;
  class A1,A2,A3 ok;
  class B1,B2,B3,B4,B5,B6 ng;

이 그림의 점선(금지)이 한 줄이라도 통과하면 boundary lint가 PR을 막아야 합니다.


3. Task Graph와 캐시

왜 중요한가: 작업의 입력과 출력을 명시해야 "이 일을 다시 할 필요가 있는가"를 기계가 판단할 수 있습니다. task graph는 일의 순서이고, 캐시는 반복 노동을 줄이는 메모입니다.

작업 입력 출력
lint source, config, lockfile report
typecheck source, tsconfig, generated types report
test source, test, fixtures coverage/report
build source, config, env schema dist
storybook/docs component, stories, tokens static docs

캐시는 deterministic해야 합니다. 현재 시간, 랜덤 값, 외부 네트워크, 로컬 절대 경로가 output에 섞이면 캐시 신뢰도가 떨어집니다.

3.1 Task Graph 실행 순서

flowchart TD
  subgraph Stage1["1단계: 의존성 준비"]
    Install[install / restore lockfile]
  end
  subgraph Stage2["2단계: 정적 검증"]
    Lint[lint]
    Type[typecheck]
  end
  subgraph Stage3["3단계: 빌드 산출"]
    BuildLibs[build shared/feature]
    BuildApps[build apps]
  end
  subgraph Stage4["4단계: 검증"]
    Unit[unit/integration test]
    E2E[E2E test]
  end

  Install --> Lint
  Install --> Type
  Lint --> BuildLibs
  Type --> BuildLibs
  BuildLibs --> BuildApps
  BuildLibs --> Unit
  BuildApps --> E2E
  Unit --> Done[CI green]
  E2E --> Done

병렬 가능 지점(같은 stage)과 직렬 강제 지점(stage 간)을 분명히 둬야 캐시 hit이 극대화됩니다.


4. Dependency 운영

왜 중요한가: 의존성 한 줄이 번들 크기·보안 패치 SLA·라이선스 의무까지 동시에 결정합니다. 추가는 쉽지만 회수는 어렵습니다.

항목 기준
공통 버전 핵심 runtime과 build tool은 중앙에서 관리
중복 의존성 bundle과 security 관점에서 주기적으로 확인
외부 패키지 license, maintenance, bundle impact 확인
peer dependency UI/runtime 패키지는 peer 범위 명확화
generated code 생성 위치와 재생성 명령 문서화

의존성 추가는 "개발 편의"뿐 아니라 bundle, 보안, 소유권 비용을 함께 봅니다.

4.1 의존성 도입 검토 흐름

일상 비유: 새 가구를 사기 전 "치수, 청소 비용, 처분 비용"을 함께 보는 것과 같습니다.

flowchart TD
  Need[새 의존성 후보] --> Check1{이미 비슷한 게 있는가?}
  Check1 -->|예| Reuse[기존 활용]
  Check1 -->|아니오| Check2{유지보수 활성?}
  Check2 -->|아니오| Reject[도입 보류]
  Check2 -->|예| Check3{라이선스 호환?}
  Check3 -->|아니오| Reject
  Check3 -->|예| Check4{번들 임팩트 허용 범위?}
  Check4 -->|아니오| Reject
  Check4 -->|예| Check5{보안 history 양호?}
  Check5 -->|아니오| Reject
  Check5 -->|예| Adopt["도입 + RFC/ADR + owner 명시"]

5. Release 전략

왜 중요한가: 같은 모노레포라도 "전체 동기 출시"인지 "팀별 개별 출시"인지에 따라 변경 위험과 협업 비용이 완전히 달라집니다.

전략 적합한 경우
Fixed version 전체 제품군을 같은 버전으로 배포
Independent version 패키지별 release cadence가 다름
App-only release 내부 패키지는 versioning 없이 app artifact로만 배포

어떤 전략이든 release note에는 변경 패키지, 영향 앱, migration 필요 여부, rollback 가능성을 포함합니다.

5.1 릴리스 전략 비교

항목 Fixed Independent App-only
버전 정렬 모든 패키지 동일 패키지별 상이 패키지 버전 없음
변경 추적 단순 (한 줄 changelog) 패키지별 changelog app 단위 changelog
외부 공개 어려움 (강결합) 가장 적합 불가능
Migration 부담 모두 함께 이동 소비자가 시점 선택 항상 최신
적합 사례 단일 제품군 디자인 시스템 등 외부 공개 사내 전용 모노레포

5.2 릴리스 상태 머신

일상 비유: 출판사가 책을 인쇄하기 전 교정 → 시쇄 → 본인쇄 → 배포로 단계를 끊는 것과 같습니다. 단계마다 되돌릴 수 있어야 합니다.

stateDiagram-v2
  [*] --> Drafted: changeset 작성
  Drafted --> Reviewed: PR review
  Reviewed --> Canary: 카나리 배포
  Canary --> Verified: smoke + 관측 정상
  Canary --> RolledBack: 회귀 감지
  Verified --> Released: 안정 배포
  Released --> Deprecated: 후속 메이저 출시
  RolledBack --> Drafted: 원인 수정 후 재시도
  Deprecated --> [*]

6. CI 최적화

왜 중요한가: 모노레포가 커지면 PR 한 건이 30분짜리 CI를 깨우는 일이 잦아집니다. 최적화의 목표는 "빠르게"가 아니라 "꼭 필요한 작업만 신뢰성 있게" 돌리는 것입니다.

최적화 기준
Affected execution 변경된 패키지와 의존 앱만 실행
Remote/local cache deterministic output만 저장
Test sharding 긴 테스트는 shard 분리
Parallelism task dependency를 지키는 범위에서 병렬화
Artifact reuse 한 번 만든 build artifact 재사용

빠른 CI보다 중요한 것은 신뢰할 수 있는 CI입니다. flaky 테스트와 잘못된 캐시는 전체 모노레포 생산성을 떨어뜨립니다.

6.1 CI 파이프라인 시퀀스

sequenceDiagram
  participant Dev as 개발자
  participant Git as Git 서버
  participant CI as CI 러너
  participant Cache as 원격 캐시
  participant Reg as 패키지 레지스트리

  Dev->>Git: PR push
  Git->>CI: webhook trigger
  CI->>CI: changed files 계산
  CI->>Cache: 캐시 키 조회
  alt 캐시 HIT
    Cache-->>CI: 산출물 반환
  else 캐시 MISS
    CI->>CI: lint/test/build 실행
    CI->>Cache: 산출물 저장
  end
  CI->>Git: 상태 보고 (green/red)
  alt 메인 브랜치 머지
    Git->>CI: release pipeline 트리거
    CI->>Reg: 패키지 publish
    CI->>Dev: release note 알림
  end

7. 체크리스트

  • [ ] 모든 app/package에 owner가 있는가
  • [ ] public API와 internal import 규칙이 자동 검증되는가
  • [ ] circular dependency가 CI에서 차단되는가
  • [ ] affected graph가 build/test/lint 범위를 계산하는가
  • [ ] 캐시 output이 deterministic한가
  • [ ] dependency 추가 기준과 license/security 검사가 있는가
  • [ ] release note에 영향 앱과 migration 정보가 포함되는가

8. 제외한 벤더 종속 항목

공통 개발 가이드에는 특정 모노레포 SaaS, 특정 원격 캐시 제공자, 특정 CI 플랫폼, 특정 클라우드 스토리지, 특정 release bot의 설정을 표준으로 포함하지 않습니다. 이 문서에는 어떤 툴체인을 쓰더라도 유지되어야 하는 ownership, boundary, task graph, cache, release governance 기준만 남깁니다.


실무 적용 가이드

언제 이 문서를 펼칠까

  • 패키지 경계가 흐려져 import가 얽힐 때
  • CI가 모든 앱과 패키지를 매번 빌드해 느릴 때
  • 공통 패키지 변경이 소비 앱을 예측 못 하게 깨뜨릴 때

적용 순서

  1. package owner와 public API를 먼저 정한다.
  2. 함께 수정되는 기능 코드는 같은 package 또는 feature 폴더에 둔다.
  3. package 간 import rule과 dependency direction을 lint로 막는다.
  4. affected graph로 test/build 범위를 계산한다.
  5. changeset/changelog와 consumer smoke를 release gate로 둔다.
flowchart LR
  S1[1. Owner/Public API 확정] --> S2[2. 코드 응집도로 폴더 정리]
  S2 --> S3[3. boundary lint 활성화]
  S3 --> S4[4. affected graph 도입]
  S4 --> S5[5. changeset + smoke gate]
  S5 --> Loop[반복 개선]
  Loop --> S1

함께 두는 파일

  • 패키지 안의 source, test, story, fixture, migration note를 함께 둔다.
  • 앱 전용 코드는 공통 package로 올리지 않는다.
  • 공통 package는 사용처, owner, release policy가 있을 때만 만든다.

흔한 실수

  • 모든 shared 코드를 하나의 utils package에 넣는다.
  • package 내부 파일 deep import를 허용한다.
  • affected test 없이 캐시만 믿는다.
  • breaking change에 consumer migration을 남기지 않는다.

PR 완료 기준

  • [ ] owner와 public API가 있다.
  • [ ] boundary lint가 통과한다.
  • [ ] affected test/build가 실행된다.
  • [ ] release note와 consumer smoke가 있다.

추천 항목 실행 우선순위 매핑

  • P1(7일 내) — package owner, task graph, affected build/test 중 하나를 작은 변경 1건에 적용하고 증거(affected graph)를 남긴다.
  • P2(30일 내) — 모노레포 기준을 팀 템플릿, 체크리스트, CI 중 한 곳에 고정한다.
  • P3(90일 내) — 불필요한 rebuild, cross-package breakage, owner 누락 추이를 보고 기준을 유지할지 조정할지 결정한다.
  • 완료 기준 — 모노레포 오너가 증거와 철회 조건을 확인했다는 기록을 남긴다.

추천 항목 실행 체크리스트

  • [ ] 1단계(7일) : package owner, task graph, affected build/test 적용 대상을 1개로 좁힌다.
  • [ ] 2단계(30일) : 증거(affected graph, boundary lint, release plan, package owner 승인)를 PR, ADR, 회고 중 한 곳에 연결한다.
  • [ ] 3단계(60일) : 불필요한 rebuild, cross-package breakage, owner 누락가 기준 안에 들어왔는지 확인한다.
  • [ ] 문제 대응 : 미달성 사유와 다음 조치, 중단 여부를 같은 기록에 남긴다.

추천 항목 실행 운영 규칙

  • 실행 게이트 : package boundary와 owner가 변경 diff에 맞게 갱신됐는지 확인한다.
  • 승인 체계 : 모노레포 오너가 영향 범위와 rollback 담당자를 적용 전에 확인한다.
  • 재개 조건 : affected build/test가 통과하면 release path를 확정한다.
  • 정지 조건 : owner 없는 shared package 변경이나 circular dependency가 있으면 중단한다.
  • 리스크 점수 : 영향 package 수, task graph 깊이, release coupling으로 산정한다.
  • 리더 승인자 : 모노레포 리드가 최종 승인 책임을 맡는다.
  • 승인 역할 : 모노레포 작성자, 검토자, 운영 확인자를 분리해 기록한다.
  • 재평가 주기 : 분기마다 package graph와 owner mapping을 점검한다.