MonoRepo & Yarn Berry

0. 개요

  • 최근 들어 프론트엔드 프로젝트 형상관리에서 고전적 소프트웨어 개발 방식인 모놀리식 애플리케이션(Monolithic)의 한계점이 나오면서 다른 방식들이 많이 나오고 있다.

  • 해당 글은, 소스 형상 관리 시스템 상에서 소스 레파지토리를 관리하는 방법에 대한 전반적인 내용과 최근 트렌드로 자리 잡혀가고 있는 MonoRepo 에 관한 설명과 관련 툴(yarn berry)을 소개하는 글이다.

1. Monolithic Application

  • 모듈화 없이 설계되어 하나의 레파지토리 안에 하나의 버전으로 관리되어 거대한 단위로 처리됐던 방식으로 제약과 비효율적인 소프트웨어 개발 방식이다.

  • 프로젝트의 크기가 크지 않고, 개발자의 수가 적기 떄문에 한 곳에서 처리하는 것이 효율적이다.

2. MultiRepo vs MonoRepo

  1. MultiRepo 개요

    • Monolithic 구조를 모듈화하여 재사용성을 높이고 전체 교체 없이 애플리케이션의 일부를 수정 또는 교체할 수 있게 해 유지 관리를 편하게 할 수 있다.

    • 프로젝트가 거대화되면서 하나의 프로젝트에서 해결하게 되면, 높은 결합도와 낮은 응집도를 야기한다. 이를 해결하기 위해, 시스템의 각 부분을 도메인 별로 분리해서 마이크로 서비스를 구성한다.

  2. MultiRepo 특징

    • 구성된 마이크로 서비스를 각 모듈별로 효율적인 관리, 자율성이 높은 독립적인 개발, 파이프라인 등을 구성하여 각각의 저장소를 구성하는 방식이다.

    • 각각의 설정 파일, 테스트, 빌드, 배포 파이프라인을 각각의 저장소에서 관리한다.

    1. Micro Frontend

      • 하나의 앱에서 각각 일부 앱을 담당해 개발을 진행하는 것이 아닌, 각 모듈별로 데이터베이스에서부터 UI에 이르기까지 end-to-end 를 개발하는 것을 말한다.
    2. MultiRepo 문제점

      • 번거로운 프로젝트 생성 : 새로운 모듈이 생성될 때 마다 개발 환경 구축, CI/CD 구축 등 다양한 과정이 필요하다.

      • 모듈의 중복 코드 가능성 : 프로젝트를 생성할 때 마다 위와 같은 개발 환경, CI/CD 구축 설정 파일들을 각 모듈별로 가지고 있어야 한다. Boilerplate Code 를 이용하여 자동화를 시켜 생성한다고 해도, 모듈별 중복 코드가 발생한다.

      • 관리 요소 증가 : 각각의 모듈별로 설정파일을 가지고 있을 때 린트, 빌드, 배포 등 각각의 모듈별로 맞춰야할 것 들이 생기면서 불편함이 생긴다.

      • dependency 지옥 : 공유가 필요한 다른 모듈이 변화했을 때, 일일이 신경을 써서 바꿔줘야하고, 실제로 통지 받거나 하지 않으면 실제 코드를 매일 확인하지 않는 이상 알기 힘들다.

      • 복잡한 테스트 방식 : 공통 모듈에 내가 필요한 기능을 개발한 경우, 공통 모듈을 수정 후 배포하고 그것을 또 내 모듈로 가져와 테스트해야하는 방식의 번거로움이 있다.

  3. MonoRepo

    • 이러한 MultiRepo 의 장점을 가져오면서 문제점을 해결할 수 있는 방법 중 하나이다.

    • MonoRepo 구조는 두개 이상의 프로젝트가 동일한 저장소에 저장되는 개발 전략이다.

    1. MonoRepo 장점

      • 더 쉬운 프로젝트 생성 : MultoRepo 에서 공유 모듈을 생성하게되면 보통 (저장소 생성 > 커미터 추가 > 개발환경 구축 > CI/CD 구축 > 빌드 > publish) 의 과정을 거친다.
        MonoRepo 에선 저장소 생성, 커미터 추가 등의 과정이 필요 없고, 개발 환경 등 devOps를 기존에 존재하는 파일을 사용하기 때문에 단축된다.

      • 더 쉬운 dependency 관리 : 공통 모듈 같은 경우 Nexus, npm registry 등 공유 저장소에 publish 하여 설치하는 과정이 필요 없으므로, 테스트를 하거나 관리하기 용이하다.

      • 관리 포인트의 단순화 : CI/CD 설정 등 DevOps 관련 설정을 한번에 변경할 수 있다.

    2. MonoRepo 문제점

      • 단일 팀 소유 원칙 위배 : 서비스, 시스템, 모듈 또는 구성요소의 소유권은 하나의 개발팀에 속해야 한다고 생각한다. 팀은 설계, 작성, 테스트, 배치, 운영, 지원 및 수정 등 구성 요소의 모든 측면에 책임을 져야 한다.

      • 대규모 리팩토링이 쉬워지는 나쁜 관행이 생김 : 대규모 리팩토링이 쉬워지는게 장점이라고 생각하는 사람들도 있지만, 이게 치명적인 단점이라고 생각하는 사람도 있다. 거대한 프로젝트에서 대규모 리팩토링을 하는 것은 굉장히 위험한 점이 있기 때문에, 여러 단계를 거치면서 각 레포지토리에 대한 Ownership을 가진 팀의 승인을 받아 진행하는 것이 좋다고 볼 수도 있다.

      • 코드 관리를 위한 노력 : MonoRepo 에서는 디펜던시를 추가하는 것이 쉽다보니, 그냥 별 생각 없이 라이브러리를 다운받고 그냥 놔두거나, 옛날에 썼지만 어쩌다보니 안쓰게 된 디펜던시들이 많이 생기다고 한다.

      • 개발 PC 사양에 따라 PC 속도가 현저히 느려질 수 있다.

  4. 결론

    • Micro Service 를 개발하는데 있어 MonoRepo, MultiRepo 는 개발 방법론 중 하나이다. 꼭 MonoRepo 를 사용해야하는 것이 아닌 비교와 선택의 문제이다.

    • 항상 MonoRepo 가 나은 방법인가 ? => MultiRepoMonoRepo의 장단점이 교차하기 때문에 적절한 상황에서 사용해야 한다. MonoRepo의 특징은 프로젝트 사이의 관계가 중요하다. 어느 정도 복잡성을 가진 (서비스/패키지 별로 나뉜) 시스템에서 유용하다. 실제 여러개의 서비스를 나누고 관리하는 것 자체가 비용이기 떄문에 생산성에 방해가 될 수도 있다. 비교적 규모가 작은 프로젝트에서는 서비스간에 경계를 명확하게 나누는 일이 어렵기도 하다. 단일 구성 요소의 동작을 격리시키는 것이 중요한지, 전체 시스템에 한번에 영향을 미칠 수 있는 것이 중요한지 잘 고민해서 선택해야 할 것 같다.

3. Why MonoRepo ?

  • MultiRepo 를 사용하여 Micro Frontend (end-to-end) 로 개발을 진행 했을 때 느낀 불편한 점들을 정리해보면,

    1. 종속성 관리

      • 각각의 MS(Micro Service) 에서 서로 사용하고 하는 모듈이나 버전 등이 다르고 서로 의존하는 별도의 모듈 이것들을 전체적으로 처리하는데 있어 복잡하다. 이로써 개발자는 여러 Repositoryclone 하여 모두 동기화된 상태를 유지하면서 개발을 해야한다. 공통 모듈을 여러 Repository 에서 사용되는 경우 최신화를 유지하지 않으면 CILocal 에서 테스트할 때 중단되지 않도록 하는 노력이 필요하다.
    2. 코드 중복이 더 자주 발생

      • 각각 MSvite.config, .npmrc, tsconfig, buildSpec 등 각각의 서비스마다 같은 설정 값을 가진 파일들이 존재하여 코드 중복이 발생한다. 그럼으로 인하여 CI/CD 에 변경이 생기거나 하는 경우, 각 담당자들이 수정을 하거나, 모든 MS 를 내려받아 CI/CD 담당자가 각각 Repository를 수정하는 번거로운 경우가 생긴다.
    3. 테스트 진행에 번거로움

      • 각각의 모듈별, 단위별 테스트로는 MultiRepo 가 테스트를 진행하기 훨씬 간편하지만 통합테스트, 공통 모듈 같은 경우에는 테스트를 하기에 MonoRepo 가 간편하다. MultiRepo 에선 각각의 MSNexus 같은 공통 저장소에 배포하고 그것을 다시 통합 App 에 설치하여 테스트를 진행하고 수정 사항이 생기면 그것을 또 다시 배포하여 설치해야하는 번거로움이 있습니다. MonoRepo 를 사용하게 되면 이러한 일말의 과정이 필요 없이 바로 수정하고 테스트가 가능하다는 장점이 있습니다.
    4. 긴 빌드 타임

      • MS 는 서비스 별로 엔트리포인트가 구분되기는 하지만, 하나의 App 패키지에서 하나의 웹팩설정으로 한번에 빌드되는 구조이다. 위 3번과 같은 상황이 발생한다. 본인이 MS 하나를 수정했을 떄, 변경이 없는 다른 모든 MS 까지 모두 새로 빌드해야하기 때문에 아까운 시간이 발생한다.
  • 해당 특징들에 따라 MonoRepo 를 사용하는 이점이 있지만, 분명 단점도 존재한다. 그래서 MonoRepo 의 장점을 가져오면서 단점을 조금이라도 해결할 수 있다면, 좋은 개발 방식이 될 것이다.

Yarn Berry

  • MonoRepo 를 사용하도록 도와주는 여러가지 Tool (yarn berry, yarn workspace, lerna 등) 이 있다. 그 중에서도 node_modules 의 단점을 해결해주고 MonoRepo 가 거대해졌을 때 빌드시간을 줄여줄 수 있는 Toolyarn berry 에 관련하여 알아보도록 하겠다.

  • Yarn Berryyarn 의 두 번째 버전으로, 2018년 9월 yarn의 RFC 저장소에서 시작되었다. Yarn 1.x 의 주요 개발자인 Mael Nison에 의해 TypeScript 로 개발되었고 2020년 1월 25일 정식 버전이 출시되었다.

  • MonoRepo 의 가장 두드러지는 단점을 보자면

    1. Repository의 거대화 : 분산되어 있던 모든 리소스를 하나의 레포로 합치면서 사이즈가 커지게된다.
    2. 느린 CI Build : CI가 하나로 구성된다는 장점은 규모가 커짐에 따라 분산된 CI 빌드보다 속도가 느릴 수밖에 없다.
    3. 무분별한 dependency : Packagedependency 관리가 쉽지만, 오히려 과도한 dependency 관계 발생 가능
  • 이 때, 느린 build 시간을 줄이기 위해 사용되는 yarn berryzero-install 이 작동하는 방식이다.

1. Zero install

  • Zero install 을 설명하기 전에, npm의 node_modules 의 단점을 훑고 갈 필요가 있다.
  1. dependency 탐색 알고리즘의 비효율

    • NPMFile System 을 통해 dependency를 관리한다. node.js에서 require() 함수를 실행하면 모듈을 찾을 때까지 상위 node_modules 디렉터리를 순회한다. 이때 느린 디스크 I/O 동작이 경로의 깊이만큼 발생한다.

    • 경우에 따라서는 I/O 호출이 중간에 실패하기도 한다. TypeScript 4.0까지는 node_modules를 이용한 패키지 탐색이 너무 비효율적인 나머지, 패키지를 처음으로 import 하기 전까지는 node_modules 내부의 타입 정보를 찾아보지 않기도 한다.

  2. 비효율적인 설치

    • 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)

  1. 개요

    • 기본적으로 yarn berryyarn 2버전에서 지원한다. yarn 1버전에선 package.json 파일을 기반으로 dependency 트리를 생성하고, 디스크에 node_modules 디렉토리 구조를 만든다. 이미 패키지의 dependency 구조를 알고 있다는 뜻이다. File System 을 이용한 관리는 꺠지기 쉽다.

    • 이러한 node_modules의 단점을 극복하기 위해, 압축 파일을 통한 패키지 dependency 관리, pnp를 통한 zero-install

  2. PnP ?

    • Berrynode_modules에 패키지 파일을 저장하는 대신 패키지의 압축 파일을 .yarn/cache 폴더에 수평적으로 저장하는 방식으로 위 문제를 해결했다. 이 방식을 YarnPlug’n’Play (PnP)라고 부른다. 압축 파일은 ZipFS를 이용하며 해당 모듈 로드가 필요할 때 메모리에서 압축을 해제하여 접근한다.
  3. 동작 방식

  • Yarn Berrynode_modules를 생성하지 않습니다. 대신 .yarn/cache 폴더에 의존성의 정보가 저장되고, .pnp.cjs 파일에 의존성을 찾을 수 있는 정보가 기록됩니다. .pnp.cjs를 이용하면 디스크 I/O 없이 어떤 패키지가 어떤 라이브러리에 의존하는지, 각 라이브러리는 어디에 위치하는지를 바로 알 수 있다.

    • YarnNode.js가 제공하는 require() 문의 동작을 덮어씀으로써 효율적으로 패키지를 찾을 수 있다. 따라서 Berry 기반의 프로젝트는 node src/main.js와 같은 명령으로는 실행할 수 없고 yarn node src/main.js와 같이 Yarn을 통해서 실행해야한다.
  1. ZipFS (Zip Filesystem)

    • Yarn PnP 시스템에서 각 의존성은 Zip 아카이브로 관리된다.
    • 이후 .pnp.cjs 파일이 지정하는 바에 따라 동적으로 Zip 아카이브의 내용이 참조된다.
    1. 장점
      • 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 사용 사례


yeobang


[참조]

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