MonoRepo & Yarn Berry
0. 개요
-
최근 들어 프론트엔드 프로젝트 형상관리에서 고전적 소프트웨어 개발 방식인 모놀리식 애플리케이션(Monolithic)의 한계점이 나오면서 다른 방식들이 많이 나오고 있다.
-
해당 글은, 소스 형상 관리 시스템 상에서 소스 레파지토리를 관리하는 방법에 대한 전반적인 내용과 최근 트렌드로 자리 잡혀가고 있는 MonoRepo 에 관한 설명과 관련 툴(yarn berry)을 소개하는 글이다.
1. Monolithic Application
-
모듈화 없이 설계되어 하나의 레파지토리 안에 하나의 버전으로 관리되어 거대한 단위로 처리됐던 방식으로 제약과 비효율적인 소프트웨어 개발 방식이다.
-
프로젝트의 크기가 크지 않고, 개발자의 수가 적기 떄문에 한 곳에서 처리하는 것이 효율적이다.
2. MultiRepo
vs MonoRepo
-
MultiRepo 개요
-
Monolithic 구조를 모듈화하여 재사용성을 높이고 전체 교체 없이 애플리케이션의 일부를 수정 또는 교체할 수 있게 해 유지 관리를 편하게 할 수 있다.
-
프로젝트가 거대화되면서 하나의 프로젝트에서 해결하게 되면, 높은 결합도와 낮은 응집도를 야기한다. 이를 해결하기 위해, 시스템의 각 부분을 도메인 별로 분리해서 마이크로 서비스를 구성한다.
-
-
MultiRepo 특징
-
구성된 마이크로 서비스를 각 모듈별로 효율적인 관리, 자율성이 높은 독립적인 개발, 파이프라인 등을 구성하여 각각의 저장소를 구성하는 방식이다.
-
각각의 설정 파일, 테스트, 빌드, 배포 파이프라인을 각각의 저장소에서 관리한다.
-
Micro Frontend
- 하나의 앱에서 각각 일부 앱을 담당해 개발을 진행하는 것이 아닌, 각 모듈별로 데이터베이스에서부터 UI에 이르기까지 end-to-end 를 개발하는 것을 말한다.
-
MultiRepo 문제점
-
번거로운 프로젝트 생성 : 새로운 모듈이 생성될 때 마다 개발 환경 구축, CI/CD 구축 등 다양한 과정이 필요하다.
-
모듈의 중복 코드 가능성 : 프로젝트를 생성할 때 마다 위와 같은 개발 환경, CI/CD 구축 설정 파일들을 각 모듈별로 가지고 있어야 한다. Boilerplate Code 를 이용하여 자동화를 시켜 생성한다고 해도, 모듈별 중복 코드가 발생한다.
-
관리 요소 증가 : 각각의 모듈별로 설정파일을 가지고 있을 때 린트, 빌드, 배포 등 각각의 모듈별로 맞춰야할 것 들이 생기면서 불편함이 생긴다.
-
dependency 지옥 : 공유가 필요한 다른 모듈이 변화했을 때, 일일이 신경을 써서 바꿔줘야하고, 실제로 통지 받거나 하지 않으면 실제 코드를 매일 확인하지 않는 이상 알기 힘들다.
-
복잡한 테스트 방식 : 공통 모듈에 내가 필요한 기능을 개발한 경우, 공통 모듈을 수정 후 배포하고 그것을 또 내 모듈로 가져와 테스트해야하는 방식의 번거로움이 있다.
-
-
-
MonoRepo
-
이러한 MultiRepo 의 장점을 가져오면서 문제점을 해결할 수 있는 방법 중 하나이다.
-
MonoRepo 구조는 두개 이상의 프로젝트가 동일한 저장소에 저장되는 개발 전략이다.
-
MonoRepo 장점
-
더 쉬운 프로젝트 생성 : MultoRepo 에서 공유 모듈을 생성하게되면 보통 (저장소 생성 > 커미터 추가 > 개발환경 구축 > CI/CD 구축 > 빌드 > publish) 의 과정을 거친다.
MonoRepo 에선 저장소 생성, 커미터 추가 등의 과정이 필요 없고, 개발 환경 등 devOps를 기존에 존재하는 파일을 사용하기 때문에 단축된다. -
더 쉬운 dependency 관리 : 공통 모듈 같은 경우 Nexus, npm registry 등 공유 저장소에 publish 하여 설치하는 과정이 필요 없으므로, 테스트를 하거나 관리하기 용이하다.
-
관리 포인트의 단순화 : CI/CD 설정 등 DevOps 관련 설정을 한번에 변경할 수 있다.
-
-
MonoRepo 문제점
-
단일 팀 소유 원칙 위배 : 서비스, 시스템, 모듈 또는 구성요소의 소유권은 하나의 개발팀에 속해야 한다고 생각한다. 팀은 설계, 작성, 테스트, 배치, 운영, 지원 및 수정 등 구성 요소의 모든 측면에 책임을 져야 한다.
-
대규모 리팩토링이 쉬워지는 나쁜 관행이 생김 : 대규모 리팩토링이 쉬워지는게 장점이라고 생각하는 사람들도 있지만, 이게 치명적인 단점이라고 생각하는 사람도 있다. 거대한 프로젝트에서 대규모 리팩토링을 하는 것은 굉장히 위험한 점이 있기 때문에, 여러 단계를 거치면서 각 레포지토리에 대한 Ownership을 가진 팀의 승인을 받아 진행하는 것이 좋다고 볼 수도 있다.
-
코드 관리를 위한 노력 : MonoRepo 에서는 디펜던시를 추가하는 것이 쉽다보니, 그냥 별 생각 없이 라이브러리를 다운받고 그냥 놔두거나, 옛날에 썼지만 어쩌다보니 안쓰게 된 디펜던시들이 많이 생기다고 한다.
-
개발 PC 사양에 따라 PC 속도가 현저히 느려질 수 있다.
-
-
-
결론
-
Micro Service 를 개발하는데 있어 MonoRepo, MultiRepo 는 개발 방법론 중 하나이다. 꼭 MonoRepo 를 사용해야하는 것이 아닌 비교와 선택의 문제이다.
-
항상 MonoRepo 가 나은 방법인가 ? => MultiRepo 와 MonoRepo의 장단점이 교차하기 때문에 적절한 상황에서 사용해야 한다. MonoRepo의 특징은 프로젝트 사이의 관계가 중요하다. 어느 정도 복잡성을 가진 (서비스/패키지 별로 나뉜) 시스템에서 유용하다. 실제 여러개의 서비스를 나누고 관리하는 것 자체가 비용이기 떄문에 생산성에 방해가 될 수도 있다. 비교적 규모가 작은 프로젝트에서는 서비스간에 경계를 명확하게 나누는 일이 어렵기도 하다. 단일 구성 요소의 동작을 격리시키는 것이 중요한지, 전체 시스템에 한번에 영향을 미칠 수 있는 것이 중요한지 잘 고민해서 선택해야 할 것 같다.
-
3. Why MonoRepo ?
-
MultiRepo 를 사용하여 Micro Frontend (end-to-end) 로 개발을 진행 했을 때 느낀 불편한 점들을 정리해보면,
-
종속성 관리
- 각각의 MS(Micro Service) 에서 서로 사용하고 하는 모듈이나 버전 등이 다르고 서로 의존하는 별도의 모듈 이것들을 전체적으로 처리하는데 있어 복잡하다. 이로써 개발자는 여러 Repository 를 clone 하여 모두 동기화된 상태를 유지하면서 개발을 해야한다. 공통 모듈을 여러 Repository 에서 사용되는 경우 최신화를 유지하지 않으면 CI나 Local 에서 테스트할 때 중단되지 않도록 하는 노력이 필요하다.
-
코드 중복이 더 자주 발생
- 각각 MS 에 vite.config, .npmrc, tsconfig, buildSpec 등 각각의 서비스마다 같은 설정 값을 가진 파일들이 존재하여 코드 중복이 발생한다. 그럼으로 인하여 CI/CD 에 변경이 생기거나 하는 경우, 각 담당자들이 수정을 하거나, 모든 MS 를 내려받아 CI/CD 담당자가 각각 Repository를 수정하는 번거로운 경우가 생긴다.
-
테스트 진행에 번거로움
- 각각의 모듈별, 단위별 테스트로는 MultiRepo 가 테스트를 진행하기 훨씬 간편하지만 통합테스트, 공통 모듈 같은 경우에는 테스트를 하기에 MonoRepo 가 간편하다. MultiRepo 에선 각각의 MS 를 Nexus 같은 공통 저장소에 배포하고 그것을 다시 통합 App 에 설치하여 테스트를 진행하고 수정 사항이 생기면 그것을 또 다시 배포하여 설치해야하는 번거로움이 있습니다. MonoRepo 를 사용하게 되면 이러한 일말의 과정이 필요 없이 바로 수정하고 테스트가 가능하다는 장점이 있습니다.
-
긴 빌드 타임
- 각 MS 는 서비스 별로 엔트리포인트가 구분되기는 하지만, 하나의 App 패키지에서 하나의 웹팩설정으로 한번에 빌드되는 구조이다. 위 3번과 같은 상황이 발생한다. 본인이 MS 하나를 수정했을 떄, 변경이 없는 다른 모든 MS 까지 모두 새로 빌드해야하기 때문에 아까운 시간이 발생한다.
-
-
해당 특징들에 따라 MonoRepo 를 사용하는 이점이 있지만, 분명 단점도 존재한다. 그래서 MonoRepo 의 장점을 가져오면서 단점을 조금이라도 해결할 수 있다면, 좋은 개발 방식이 될 것이다.
Yarn Berry
-
MonoRepo 를 사용하도록 도와주는 여러가지 Tool (yarn berry, yarn workspace, lerna 등) 이 있다. 그 중에서도 node_modules 의 단점을 해결해주고 MonoRepo 가 거대해졌을 때 빌드시간을 줄여줄 수 있는 Tool 인 yarn berry 에 관련하여 알아보도록 하겠다.
-
Yarn Berry 는 yarn 의 두 번째 버전으로, 2018년 9월 yarn의 RFC 저장소에서 시작되었다. Yarn 1.x 의 주요 개발자인 Mael Nison에 의해 TypeScript 로 개발되었고 2020년 1월 25일 정식 버전이 출시되었다.
-
MonoRepo 의 가장 두드러지는 단점을 보자면
- Repository의 거대화 : 분산되어 있던 모든 리소스를 하나의 레포로 합치면서 사이즈가 커지게된다.
- 느린 CI Build : CI가 하나로 구성된다는 장점은 규모가 커짐에 따라 분산된 CI 빌드보다 속도가 느릴 수밖에 없다.
- 무분별한 dependency : Package 간 dependency 관리가 쉽지만, 오히려 과도한 dependency 관계 발생 가능
-
이 때, 느린 build 시간을 줄이기 위해 사용되는 yarn berry 의 zero-install 이 작동하는 방식이다.
1. Zero install
- Zero install 을 설명하기 전에, npm의 node_modules 의 단점을 훑고 갈 필요가 있다.
-
dependency 탐색 알고리즘의 비효율
-
NPM 은 File System 을 통해 dependency를 관리한다. node.js에서 require() 함수를 실행하면 모듈을 찾을 때까지 상위 node_modules 디렉터리를 순회한다. 이때 느린 디스크 I/O 동작이 경로의 깊이만큼 발생한다.
-
경우에 따라서는 I/O 호출이 중간에 실패하기도 한다. TypeScript 4.0까지는 node_modules를 이용한 패키지 탐색이 너무 비효율적인 나머지, 패키지를 처음으로 import 하기 전까지는 node_modules 내부의 타입 정보를 찾아보지 않기도 한다.
-
-
비효율적인 설치
-
NPM에서 구성하는 node_modules 디렉토리 구조는 매우 큰 공간을 차지한다. 일반적으로 간단한 Frontend 프로젝트도 수백 메가바이트의 node_modules 폴더가 필요하다. 용량만 많이 차지할 뿐 아니라, 큰 node_modules 디렉토리 구조를 만들기 위해서는 많은 I/O 작업이 필요하다.
-
이렇게 깊은 트리 구조에서 dependency이 잘 설치되어 있는지 검증하려면 많은 수의 I/O 호출이 필요하다. 일반적으로 디스크 I/O 호출은 메모리의 자료구조를 다루는 것보다 훨씬 느리다. 이런 문제로 인해 Yarn v1이나 NPM은 기본적인 dependency 트리의 유효성까지만 검증하고, 각 패키지의 내용이 올바른지는 확인하지 않는다.
-
- 따라서, node_modules 를 없애고 압축 파일로 dependency를 관리하고 이 파일을 git으로 관리하면 설치 과정을 제거하여 CI 시간을 줄일 수 있는데, 이것을 Zero Install 이라고 한다.
2. Plug And Play(pnp)
-
개요
-
기본적으로 yarn berry 는 yarn 2버전에서 지원한다. yarn 1버전에선 package.json 파일을 기반으로 dependency 트리를 생성하고, 디스크에 node_modules 디렉토리 구조를 만든다. 이미 패키지의 dependency 구조를 알고 있다는 뜻이다. File System 을 이용한 관리는 꺠지기 쉽다.
-
이러한 node_modules의 단점을 극복하기 위해, 압축 파일을 통한 패키지 dependency 관리, pnp를 통한 zero-install 을
-
-
PnP ?
- Berry는 node_modules에 패키지 파일을 저장하는 대신 패키지의 압축 파일을 .yarn/cache 폴더에 수평적으로 저장하는 방식으로 위 문제를 해결했다. 이 방식을 Yarn은 Plug’n’Play (PnP)라고 부른다. 압축 파일은 ZipFS를 이용하며 해당 모듈 로드가 필요할 때 메모리에서 압축을 해제하여 접근한다.
-
동작 방식
-
Yarn Berry는 node_modules를 생성하지 않습니다. 대신 .yarn/cache 폴더에 의존성의 정보가 저장되고, .pnp.cjs 파일에 의존성을 찾을 수 있는 정보가 기록됩니다. .pnp.cjs를 이용하면 디스크 I/O 없이 어떤 패키지가 어떤 라이브러리에 의존하는지, 각 라이브러리는 어디에 위치하는지를 바로 알 수 있다.
- Yarn은 Node.js가 제공하는 require() 문의 동작을 덮어씀으로써 효율적으로 패키지를 찾을 수 있다. 따라서 Berry 기반의 프로젝트는 node src/main.js와 같은 명령으로는 실행할 수 없고 yarn node src/main.js와 같이 Yarn을 통해서 실행해야한다.
-
ZipFS (Zip Filesystem)
- Yarn PnP 시스템에서 각 의존성은 Zip 아카이브로 관리된다.
- 이후 .pnp.cjs 파일이 지정하는 바에 따라 동적으로 Zip 아카이브의 내용이 참조된다.
장점
-
node_modules 디렉토리 구조를 생성할 필요가 없기 때문에 설치가 빠르다.
yarn berry:
-
yarn v1:
- 각 패키지는 버전마다 하나의 Zip 아카이브만을 가지기 때문에 중복해서 설치되지 않고, 각 Zip 아카이브가 압축되어 있으므로, 스토리지 용량을 크게 아낄 수 있다.
-
실제 같은 package.json 으로 yarn v1 (node_modules) 과 v2 (yarn berry cache) 를 사용해서 install 하였을 때, dependency 가 설치된 폴더의 크기를 비교해 봤을 떄, 차이가 많이 나는 것을 알 수 있다.
-
의존성을 구성하는 파일의 수가 많지 않아 변경 사항을 감지하거나 전체 의존성을 삭제하는 작업도 속도가 빠르다.
3. Why yarn berry ?
-
Zero-Install, PnP 방식 등의 이점을 정리해보자면 dependency 검색, 설치, 관리에 대한 이점과 git clone, branch checkout 등을 실행 했을 시에 yarn install 을 하지 않아도 dependency 를 사용 가능하다.경우에 따라서는 잘못된 의존성 버전이 사용됨으로써 웹 서비스가 알 수 없는 이유로 오동작하는 경우도 완전히 해결된다.
-
그리고, CI에서 의존성 설치하는 시간을 크게 절약할 수 있다. Zero-Install을 사용하면 Git Clone으로 저장소를 복제했을 때 의존성들이 바로 사용 가능한 상태가 되어, 의존성을 설치할 필요가 없다. 이로써 CI 시간을 크게 절약할 수 있다.
정리
-
앞서 소개한 내용처럼 서비스를 개발하다 느낀 문제들을 MonoRepo 를 통해 해결하여 간결한 테스트와 빌드 등 여러가지 이점을 챙기는 대신, 거대해지는 Repository 와 긴 CI/CD 시간을 Yarn berry 를 통해 해결한다면 조금 더 완만하게 개발을 진행할 수 있을 것이다.
-
비록 MonoRepo 를 사용하는 것을 반대하는 측의 의견을 확인해보면 단일 팀 소유 원칙 즉, 서비스, 모듈 또는 구성요소의 소유권은 단일 개발팀에 속해야 하고. 팀은 설계, 작성, 테스트, 배치, 운영, 지원 및 수정 등 구성 요소의 모든 측면에 책임을 져야 하므로 MonoRepo 를 사용하는 것이 위험하다고 한다.
-
또한, 프로젝트에 대규모로 리팩토링이 필요한 경우, 하나의 repository 에서 진행하니 오히려 쉬워지니 좋다고 하는 의견도 있지만, 대규모 리팩토링이 쉬워지면 리팩토링에 대한 전체 팀에 대한 조율이나 큰 고민 없이 변경되고 구현도 쉽게 해버려 좋은 경우가 아니라고 생각한다고 한다. 리팩토링을 단축하면 예상치 못한 문제가 발생할 수도 있고, 팀 간 커뮤니케이션과 신뢰도 저하될 수 있다.
-
따라서 MonoRepo 를 사용한다고 하면 각 팀들과의 확실한 커뮤니케이션과 원칙을 잘 정해놓고 사용하면 MonoRepo 이점들을 충분히 사용할 수 있을 것이다.
MonoRepo 사용 사례
- Google - Why Google Stores BillionsofLines of Code in a Single Repository(Video)
- Gogle - Why Google Stores Billions of Lines of Code in a SingleRepository
- Facebook -Scaling Mercurial at Facebook
- Uber - Uber Technology Day: MonorepotoMultirepo and Back Again(Video)
- Uber - The Journey ToAndroidMonorepo: The History Of Uber Engineering’s Android CodebaseOrganization
- Airbnb - From Monorail toMonorepo:Airbnb’s journey into Microservices
- Spotify-Segment’s transition back to a monorepo(Video)
- Netflix - Towards true continuous integration: distributedrepositoriesand dependencies
[참조]
https://tech.buzzvil.com/handbook/multirepo-vs-monorepo/
https://d2.naver.com/helloworld/0923884#ch3
https://toss.tech/article/node-modules-and-yarn-berry
https://tech.buzzvil.com/handbook/multirepo-vs-monorepo/
https://www.itworld.co.kr/insight/214234
https://channel.io/ko/blog/frontend_yarnberry
https://velog.io/@warmwhiten/yarn-berry-typescript-eslint-prettier-emotion-react