저장소는 하나였다. 안에 든 코드는 A라는 기존 템플릿에 B 템플릿을 합친 형태였다. 한 도메인 안에서 A 화면과 B 화면이 섞여 쓰여야 했고, 양쪽에 있는 스타일과 기능 코드를 서로 가져다 쓰기 편하게 하려고 아예 합쳐버린 것이다.
합친 이유는 편의였지만, 애플리케이션이 커지면서 패키지 충돌이 점점 늘었다. 처음부터 다시 설계해 나누는 게 맞다고 판단했고, 그때 고른 구조가 Monorepo다. 이 글에서는 Monorepo가 무엇인지 짧게 짚고, Nx로 나눈 뒤 달라진 점과 아직 풀리지 않은 의문을 적는다.
여러 프로젝트를 한 저장소에 두는 방식
Monorepo는 여러 프로젝트의 코드를 하나의 저장소에서 관리하는 방식이다. 큰 소프트웨어가 여러 서브 프로젝트로 나뉘어 있을 때, 서브 프로젝트마다 저장소를 따로 두는 대신 모든 코드를 한곳에 모은다.

출처: Moving from multiple repositories to a lerna-js mono-repo
그림으로 보면 차이가 분명하다. Monolith는 프로젝트 하나가 저장소 하나고, Multi-repo는 프로젝트마다 저장소가 따로 있다. Mono-repo는 저장소는 하나인데 그 안에서 프로젝트가 나뉜다. 우리 저장소는 A와 B가 경계 없이 뒤섞인 Monolith에 가까웠고, 옮겨 가려는 곳은 오른쪽 끝이었다.
장점은 세 가지로 정리된다.
버전 관리가 한곳에 모인다. 모든 코드가 한 저장소에 있으니 의존성 관리가 수월하고, 버전 충돌을 풀거나 코드베이스 전체를 한 번에 업데이트하기 쉽다. 동시에 서브 프로젝트로 경계가 나뉘기 때문에 프로젝트마다 다른 버전의 의존 패키지를 쓰는 것도 쉬워진다. 확장하기 좋은 구조라는 뜻이다.
공통 코드를 재사용하기 쉽다. 여러 곳에서 쓰는 코드나 라이브러리를 공유할 수 있어 중복이 줄어든다.
팀 간 협업이 편해진다. 다른 서브 프로젝트를 맡은 개발자도 필요한 정보를 찾고 변경 사항을 파악하기 쉽다.
단점도 있다.
규모가 커지면 관리가 복잡해진다. 저장소가 방대해질수록 빌드 시간도 늘어난다.
접근 권한을 나누기 어렵다. 여러 프로젝트가 한 저장소에 있으면 특정 부분만 접근을 막기가 까다롭고, 그만큼 보안 리스크가 커질 수 있다.
도구는 Nx로 골랐다
기존 애플리케이션을 Monorepo로 다시 짜는 데 쓸 수 있는 도구로는 Yarn workspaces, Lerna, Nx, Turborepo 등이 있다. 나는 이 중에서 Nx를 썼다.
A와 B는 apps/ 아래 각각의 앱으로 나눴고, 루트 package.json의 scripts로 앱별 실행과 빌드를 묶었다.
"scripts": {
"start:A": "npx nx serve A --port 3005",
"start:B": "npx nx serve B --port 3006",
"start:all": "npm-run-all --parallel start:A start:B",
"build:A": "npx nx run A:build",
"build:B": "npx nx run B:build",
"build:generate": "(cd apps/A && npx nuxi generate --dotenv .env.production) && (cd apps/B && npx nuxi generate)"
},
두 앱은 포트를 3005, 3006으로 나눠 따로 띄우고, 함께 띄울 때는 npm-run-all --parallel로 둘을 동시에 실행한다. 빌드는 nx run <앱>:build로 앱마다 따로 돌리는데, 정적 파일을 뽑는 build:generate만은 Nx를 거치지 않고 각 앱 디렉터리로 들어가 nuxi generate를 직접 부른다. A만 .env.production을 읽도록 --dotenv를 붙인 점도 눈여겨볼 부분이다.
나누고 나서 달라진 것
처음 목적은 의존성 충돌을 줄이는 것뿐이었는데, 실제로 얻은 건 그보다 많았다. 서브 앱끼리 상호작용하는 지점을 빠르게 찾을 수 있게 되어 작업이 수월해졌다. 불필요한 코드 중복이 크게 줄었고, 전체 프로젝트의 코드 히스토리도 한눈에 들어온다. 빌드와 배포 역시 전보다 훨씬 단순해졌다.
아직 남은 의문
모든 게 한 저장소로 모이다 보니 특정 서브 프로젝트의 커밋 히스토리만 따로 추적하는 일은 예전보다 번거로워질 것 같다.
규모도 걸린다. 우리 프로젝트는 서브 프로젝트가 2~3개뿐이라 Monorepo의 이점을 크게 봤지만, 서브 프로젝트가 훨씬 많아져도 이 방식이 효율적일지는 아직 모르겠다. 그 정도 규모라면 Multi-repo가 더 나을 수도 있다고 본다.