하나의 repository가 있다. 이 저장소의 코드는 A라는 기존 템플릿 코드에 B 템플릿 코드를 합친 형태다. 하나의 도메인 안에서 A와 B 화면이 유기적으로 사용되어야했고, A와 B에 각각에 있는 스타일이나 기능 코드를 쉽게 가져다 쓰기 위해 이렇게 병합된 형태가 되었다. 그런데 애플리케이션의 사이즈가 커지면서 점점 패키지 충돌이 발생하게 되었다. 그래서 처음부터 다시 설계하여 분리하는것이 적절하다고 판단했고, 이 때 Monolithic Repository 구조를 적용하게되었다.

Monorepo

다수의 프로젝트 코드를 하나의 리포지토리에서 관리하는 접근 방식. 예를 들어, 하나의 대형 소프트웨어 프로젝트가 여러 서브 프로젝트로 나뉘어 있을 때, 각각을 별도의 리포지토리에서 관리하는 대신 모든 코드를 한 곳에 모아두는 방식.

출처: https://levelup.gitconnected.com/moving-from-multiple-repositories-to-a-lerna-js-mono-repo-faa97aeee35b

출처: https://levelup.gitconnected.com/moving-from-multiple-repositories-to-a-lerna-js-mono-repo-faa97aeee35b

장점

  • 통합된 버전 관리: 모든 프로젝트의 코드가 한 곳에 있어, 의존성 관리가 용이. 버전 충돌을 쉽게 해결하고, 코드 베이스 전체에서 한 번에 업데이트를 진행할 수 있습니다. 또 한편으로는 서브 프로젝트로 분리되는 구조 덕분에 서로 다른 버전의 의존 패키지를 사용하기가 쉬워진다.(즉 소프트웨어 확장이 쉬워진다)

  • 재사용 용이: 공통으로 사용되는 코드나 라이브러리를 쉽게 공유하고 재사용 가능. 코드 중복 ⬇️ 효율성 ⬆️

  • 팀 간 협업 강화: 하나의 저장소에서 작업함으로써, 팀 간의 협업이 원활해진다. 서로 다른 서브 프로젝트를 관리하는 개발자들 입장에서도 필요한 정보를 쉽게 찾을 수 있게되고, 변경 사항을 쉽게 파악할 수 있다.

단점

  • 규모의 복잡성: 저장소의 크기가 방대해지면서 관리가 복잡해질 수 있다. → 빌드 시간 ⬆️

  • 보안 문제: 하나의 저장소에 여러 프로젝트가 함께 있을 경우 보안상의 리스크가 커질 수 있다. 특정 부분의 접근 권한을 제한하는 것이 더 어려워질 수 있다.

Monorepo 방식으로 구조 변경 후

Monorepo 구조로 전환 후, 전 보다 많은 부분에서 편리해졌다. 처음엔 단순히 의존성 충돌을 줄이려는 목적이었지만, 실제로 경험한 장점은 그보다 많았다. 서로 다른 서브 앱 간 상호작용이 필요한 부분을 빠르게 파악할 수 있게 되었고, 이에 따라 더 효과적으로 작업을 진행할 수 있었다. 또한, 불필요한 코드 중복을 크게 줄일 수 있었고, 전체 프로젝트의 코드 히스토리를 한눈에 파악하기도 쉬워졌다. 빌드와 배포 과정 역시 이전보다 훨씬 간단해졌고, 팀 전체의 작업 흐름이 더욱 빠르고 효율적으로 변했다.

하지만 몇가지 의문은 여전히 남아있다. 하나의 저장소에 모든 것이 통합되다보니 특정 서브 프로젝트에 대한 커밋 히스토리만 추적하는 것이 예전보다 조금 더 복잡할것 같다는 생각이 들었다. 우리 프로젝트는 2~3개의 서브 프로젝트만 포함하고 있었기에 이러한 Monorepo 구조에서 더 큰 이점을 얻을 수 있었지만, 더 많은 서브 프로젝트가 포함되는 경우에는 이 방식이 효율적일지는 여전히 의문으로 남아있다. 이 점에서는 역시 Multi-repo 방식이 더 나을 것 같다.

기존의 애플리케이션을 monorepo 구조로 재설계하기 위해 활용할 수 있는 도구로는 Yarn workspaces, Lerna, Nx, Turborepo 등이 있는데, 나는 이 중에서 nx를 사용하였다.

Sample code - package.json

"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)"
},

애플리케이션 최상위 레벨에 있는 package.json 예시