모노레포란
모노레포(Monorepo)는 여러 프로젝트나 패키지를 하나의 저장소(Repository)에 통합해 관리하는 구조를 의미한다. 기존의 여러 저장소를 각각 관리하는 멀티레포(Multirepo) 방식과 달리, 하나의 레포지토리 내에서 다양한 애플리케이션, 라이브러리, 공통 모듈을 함께 관리한다.
다시 말해서 하나의 git 레포지토리에 여러 패키지를 두고 관리하게 되는 것이다.
참고로 모노레포(Monorepo)와 모놀리식(Monolithic) 아키텍처는 다른 개념이다.
모노레포 vs 모놀리식
-
모노레포는 저장소 관리 방식이다. 여러 독립적인 프로젝트가 하나의 저장소에 공존하지만, 각 프로젝트는 개별적으로 빌드, 테스트, 배포될 수 있다.
-
모놀리식은 소프트웨어 아키텍처 스타일이다. 하나의 거대한 코드베이스로 구성된 애플리케이션으로, 모든 기능이 단일 실행 파일이나 서비스로 배포된다. 각 기능이 긴밀히 연결되어 있어 변경이 있을 때 전체 시스템에 영향을 줄 가능성이 크다.
문제 상황: 하나의 서비스와 여러 클라이언트
팀 내에서 진행중이던 프로젝트에서 하나의 서비스를 두 곳의 클라이언트에 제공하고 있었다. 대부분의 기능은 같았지만 각각의 요구사항과 디자인의 차이로 인해 하나의 서비스는 두개의 브랜치로 갈라져서 각각 관리되고 있었다.
겉으로는 하나의 레포이긴 했지만 사실상 두개의 레포처럼 두 브랜치는 만날 수 없었다. 공통적인 컴포넌트, 로직들을 분리해서 하나로 관리할 수 없었다.
우리 팀에서 모노레포는 만나지 못하던 두 브랜치의 공통 부분을 만날 수 있게 하고, 두 애플리케이션을 하나의 포인트에서 관리할 수단으로 사용되었다.
모노레포 적용 후
모듈화
패키지 간의 공통 컴포넌트, 로직은 공통 모듈을 통해 공유하도록 했다. 공통 모듈을 만들기 위해서는 코드를 재사용 가능하도록 작성하는 노력이 필수적이었 다.
리팩토링 내용
- 급히 만들었던 컴포넌트들을 다시 재사용 가능하도록 수정
- 타입에 제네릭 적용
- 공통으로 사용되는 로직을 hook 또는 util로 분리
모듈화의 이점
-
✅ 코드 재사용성 증가
공통 컴포넌트와 로직을 하나의 모듈로 분리함으로써 여러 패키지에서 동일한 코드를 반복 작성할 필요가 없어졌다. 이는 개발 생산성을 높이고, 코드 유지보수에 소요되는 시간을 줄였다. 새로운 기능을 추가하거나 기존 기능을 수정할 때 하나의 모듈만 변경하면 되므로 일관성이 유지되었다.
-
✅ 코드 품질 및 안정성 향상
모듈화된 코드는 각각의 책임이 명확하게 분리되어 있어 테스트와 디버깅이 용이하다. 공통 모듈에 대한 테스트가 완료되면, 이를 사용하는 모든 패키지에서 동일한 안정성을 보장할 수 있다.
-
✅ 확장성과 유지보수성 강화
프로젝트가 확장되더라도 공통 모듈을 쉽게 확장하거나 교체할 수 있는 구조가 갖춰졌다. 새로운 패키지를 추가할 때 기존 공통 모듈을 활용함으로써 초기 설정 시간이 단축되었으며, 유지보수 비용이 감소했다.
빠른 개발 환경 구축
새로운 프로젝트를 위한 개발 환경을 구축하기 위한 eslint, prettier, tsconfig 등의 설정을 공통화할 수 있었다.
루트 폴더의 공통적인 eslint, prettier, ts 설정들을 레포 내의 패키지들에 모두 적용할 수 있었다.
관리 포인트가 하나로
공통 컴포넌트 UI를 수정해야 하거나, 로직에 오류가 발견되었을 때 기존에는 두 개 이상의 브랜치에 각각 작업해야 했었다. 이제는 하나의 브랜치에서 관리하게 되고, 패키지마다 반복되는 코드는 이미 공통 모듈로 분리가 되었기 때문에 중복 작업이 확연히 줄었다.
패키지 매니저 pnpm
모노레포 환경을 구축하기로 결정한 후 pnpm을 패키지 매니저로 정한 가장 큰 이유는 옆 팀에서 이미 pnpm으로 모노레포 환경을 만든 적이 있었기 때문이다.
러닝 커브를 줄이고자 선택했던 pnpm이 어떤 특징이 있었는지 알아보았다.
전역 스토어, 하드 링크
pnpm은 패키지 관리의 효율성을 극대화하기 위해 전역 스토어와 하드 링크 방식을 사용한다. 패키지는 전역 스토어에 한 번만 저장되며, 각 프로젝트의 node_modules에는 해당 파일이 하드 링크로 연결된다. 이 방식은 단순히 패키지를 복사하는 대신 파일 시스템의 링크를 활용하므로 추가적인 디스크 공간을 소비하지 않는다.
디스크 절약과 설치 속도 향상
npm이나 yarn 클래식을 사용하면 프로젝트가 동일한 의존성을 사용할 때마다 별도의 복사본이 저장되므로, 프로젝트가 많을수록 디스크 공간이 낭비된다. pnpm은 content-addressable 저장소를 사용해 파일의 내용에 따라 고유하게 저장한다. 예를 들어, 100개의 프로젝트가 동일한 의존성을 사용하더라도 해당 의존성의 사본은 단일 위치에 저장되며, 이를 각 프로젝트가 하드 링크로 참조한다.
특히, 의존성의 새로운 버전에서 일부 파일만 변경될 경우, pnpm은 변경된 파일만 전역 스토어에 추가한다. 예를 들어, 100개의 파일 중 단 1개의 파일만 변경되었 을 때 전체 패키지를 복제하지 않고 해당 파일만 추가하므로 디스크 공간 사용이 최소화된다.
모노레포 환경에서의 장점
-
✅ 디스크 공간 절약
동일한 의존성을 여러 프로젝트에서 공유할 경우 불필요한 복사가 제거되어 프로젝트 수가 늘어나도 디스크 사용량이 크게 늘지 않는다.
-
✅ 빠른 설치 속도
중복 다운로드와 복사가 없기 때문에 의존성 설치 시간이 단축된다.
-
✅ 효율적인 업데이트
의존성 변경 사항이 최소한으로 저장되기 때문에 업데이트가 빠르고 효율적이다.
심볼릭 링크
pnpm은 전역 스토어와 하드 링크뿐만 아니라 심볼릭 링크를 활용하여 패키지 관리의 효율성을 더욱 높인다. 프로젝트의 node_modules 구조는 의존성 트리를 명확히 유지하면서도 각 패키지가 전역 스토어에서 심볼릭 링크로 연결되도록 구성된다.
심볼릭 링크의 장점
-
✅ 의존성 충돌 방지
npm이나 yarn 클래식 방식에서는 모든 의존성이 node_modules의 루트로 호이스팅되며, 이를 통해 예상치 못한 의존성 충돌이 발생할 수 있다. pnpm은 심볼릭 링크를 사용해 프로젝트의 직접 의존성만 루트에 배치하고, 하위 의존성은 각각의 패키지별 디렉토리에서 참조되므로 이러한 문제를 방지한다.
-
✅ 명확한 의존성 구조
심볼릭 링크로 인해 각 패키지의 의존성 구조가 실제 디렉토리 구조와 일치한다. 이로 인해 의존성 트러블슈팅이 더 직관적이며, 디버깅이 쉬워진다.
-
✅ 빠른 설치 및 업데이트
심볼릭 링크는 복사를 최소화하고, 필요 시 빠르게 참조할 수 있는 방식이기 때문에 설치 속도와 업데이트 속도가 빠르다.
모노레포에서의 심볼릭 링크 활용
모노레포 환경에서는 여러 패키지가 서로 다른 의존성 버전을 사용할 수 있는데, 심볼릭 링크 덕분에 패키지 간 의존성 충돌 없이 서로 독립적인 버전 관리가 가능하다. 이를 통해 하나의 레포에서 다양한 프로젝트를 효율적으로 관리할 수 있다.
flat하지 않은 node_modules
pnpm은 전통적인 npm이나 yarn 클래식과 달리 의존성을 평평하게(flat) 호이스트하지 않는다. 일반적으로 npm이나 yarn 클래식을 사용하면, 모든 의존성이 node_modules 디렉토리의 루트로 호이스트되어 프로젝트에 명시적으로 추가되지 않은 패키지까지도 접근할 수 있는 문제가 발생한다. 이는 예기치 않은 의존성 충돌이나 보안 문제로 이어질 수 있다.
pnpm은 이러한 문제를 방지하기 위해 symlink를 사용하여, 프로젝트가 직접적으로 명시한 의존성만 node_modules 루트에 위치시킨다. 하위 의존성은 각각의 부모 디렉토리 구조에 따라 정확히 위치되며, 각 패키지는 독립적인 의존성 트리를 유지한다.
마무리
모노레포 전환과 pnpm 도입으로 프로젝트 관리가 간소화되었다. 분리됐던 브랜치를 하나로 통합하고, 공통 모듈화를 통해 코드 재사용성과 유지보수성이 높아졌다. 덕분에 중복 작업이 줄어들고, 수정과 배포 과정도 효율적으로 개선됐다.
모노레포 구조에서는 여러 패키지 간 의존성이 쉽게 연결될 수 있지만, 그만큼 의존성 관리에 신중을 기해야 한다. 패키지 간의 의존성이 지나치게 얽히면, 하나의 패키지에서 발생한 변화가 다른 패키지들에 예기치 못한 영향을 미칠 수 있다. 따라서, 각 패키지 간의 의존 관계를 명확히 정의하고, 변경 사항에 대한 영향을 면밀히 점검하는 것이 중요하다.
필요한 상황에 잘 맞게 사용해보면서 프로젝트에 맞는 규칙과 프로세스를 발전시켜보려 한다.