Monorepo?
Monorepo는 하나의 Root repo에서 1개 이상의 repo를 모아서 구성하는 Repository 관리 방법을 뜻합니다.
1개의 애플리케이션(또는 라이브러리)가 하나의 repo와 1:1 대응하는 방식과 비교하여 다음과 같은 이점을 얻을 수 있습니다.
[1]. 중첩되는 코드의 관리가 쉬워진다
여러 repo가 공유하는 코드가 있을 경우, 코드 공유를 위한 repo를 새로 만들고 수정하기가 용이하다.
[2]. 중첩되는 의존성에 대한 중복 설치가 필요없다.
여러 repo가 동일한 의존성을 사용하는 경우, root repo에서 설치해두면 각 repo에서 따로 설치할 필요가 없이 불러올 수 있습니다.
- [단점1] root repo에서 의존성 버전을 변경하면서 관련 코드들이 상당한 양의 수정을 필요로 할 경우, 아직 변경하고 싶지 않은 repo에서도 일괄적으로 대응해야 함.
[3]. 빌드/테스트 파이프라인이 간결해진다.
패키지 매니저의 workspace 기능과 더불어 Lerna, Nx Rush, Turborepo와 같이 다양한 Monorepo 관리 도구가 있습니다.
이 도구들은 (1) 커맨드 병렬 실행 (2) 커맨드간 의존성 관리 (3) 빌드 캐싱과 같은 기능들을 제공하여 빌드, 테스트와 같이 여러 repo에 걸쳐서 실행하는 작업에 대한 파이프라인을 구성할 때 속도향상과 편의성을 모두 얻을 수 있습니다.
- (숫자 번호로 나열한 기능은 모든 도구의 기능을 종합한 것으로, 각 도구마다 지원 여부가 일부 다를 수 있습니다.)
시작하기
npm 및 npm의 대체제로 사용되는 대부분의 패키지 매니저(yarn,pnpm 등..)는 workspace 기능을 지원합니다. Lerna, Nx 등의 도구를 쓰지 않아도 Monorepo 뼈대를 구축하기에 손색이 없습니다.
NPM workspace
Yarn workspace
Pnpm workspace
나의 경험
토이프로젝트에서 2~5개의 typescript 앱을 Monorepo로 간단하게 구축해본 경험과 + 현재 재직중인 회사에서 구축하기 시작한 전사용 Monorepo를 보며 얻은 경험에 기반하여, Monorepo에서 각 repo를 불러올 때 주로 두가지 방법을 사용하게 되었습니다.
[1]. 설치하여 import하기
packages/A라는 repo에서 packages/B라는 repo를 쓰려는 경우를 예로 들면, packages/A/package.json에 다음과 같은 형태로 패키지를 설치합니다.
{
// ...
"dependencies": {
// ...
"packages/B": "workspace:*"
}
}
Vite처럼 HMR을 지원하는 개발환경이고 ts 모듈을 직접 불러온다면, 이 방식을 사용해도 코드 수정이 즉각 반영됩니다.
다만 파일 이동이 발생했을 때, VScode에서 이동을 감지하여 자동으로 제안하는 import statement 업데이트를 수락해도 제대로 동작하지 않습니다. ㅠ
VScode에서 해당 기능을 유용하게 쓰는 입장으로서, 대규모 수정이 동반되는 모듈 변경이 발생한 경우 테스트코드 없으면 극복 난이도가 높아질 것 같습니다. 사용 방식이 잘못된 것인지 찾아봐야 할 듯
[2]. 상대경로로 직접 import하기
Monorepo를 구성했다고 해도, 그저 상대경로로 다른 repo에 있는 모듈을 불러와도 충분히 동작합니다.
상대경로로 직접 참조하고 있기 때문에 파일 이동이 발생해도 VScode에서 import statement 업데이트를 수락하면 정상 동작합니다.
무엇을 얻었나
[1]. CI/CD 구성에 대한 편의성
main
| branch1 (app1)
| branch1-feat1
| branch1-feat2
| branch2 (app2, app3)
| branch2-feat1
| branch2-feat2
main은 각 배포가 최종 완료된 후에 병합하는 장소로만 두고, 하나의 branch는 하나의 서비스 배포 단위로 대응시키면 branch를 기반으로 CI/CD 트리거를 구성하여 모든 애플리케이션의 배포를 하나의 monorepo만으로 한번에 관리할 수 있습니다.
[2]. 모듈 확장성
기존에는 서로 다른 repo의 모듈을 불러올 때에는 submodule로 긁어오거나, NPM에 배포한 것을 설치해야 했습니다. NPM에 배포하여 쓰는 경우라면 versoning에 용이하다는 장점은 있으나, 우리는 많은 실수를 하는 애기개발자이기에 잦은 코드변경에 대응하기 어렵다는 단점이 더 크게 느껴집니다.
Monorepo 구조에서는 중복되는 코드를 발견했을 때 이를 모듈화하여 하나의 repo로 떼어내는 과정이 더 쉬워졌음을 느낍니다.
무엇이 부족한가
빌드 최적화 관점에서 다음과 같은 고려를 해봐야 합니다.
- ts 모듈을 직접 불러올 때와 build output을 불러올 때 빌드 시간에 얼마나 차이나는지
- 동일 repo의 로컬 모듈을 불러올 때와 동일한 수준의 빌드 캐싱이 되는 중인지
- Repo 수준의 빌드 캐싱이 되는 중인지
현재 개발중인 애플리케이션 규모에서는 SWC와 Esbuild의 트랜스파일링 성능이 너무 좋은탓에 아직까진 최적화에 대한 필요성을 느끼지 못했지만,
트랜스파일러/빌드툴을 어떤것을 쓰냐에 따라서 최적화 전략도 다를테니 규모가 커질수록 중요한 지점이 될 것으로 보입니다.
'General Programming' 카테고리의 다른 글
거대한 공룡들은 무슨 언어로 개발을 할까? - stackshare (0) | 2017.01.23 |
---|