Frontend

그래서 모노레포가 뭐지? (feat. yarn workspace)

devdubby 2023. 4. 9. 21:52

요즘 Monorepo를 검색 해보면 여기저기서 도입해본 경험에 대한 관련 글들이 많이 나온다. 필자도 현재 회사에서 Yarn workspace, Yarn Berry를 활용한 모노레포를 사용해본 경험이 있는데 그 경험과 함께 모노레포 관련 개념들을 함께 설명해보려 글을 작성한다.

그때 그시절 Yarn...(???) 😏

모노레포 소개

모노레포(Monorepo)란 여러 개의 프로젝트나 패키지를 하나의 저장소에서 관리하는 방식을 의미한다. 이 방식은 저장소를 공유함으로써 프로젝트 간의 협업, 의존성 관리, 코드 재사용 등을 쉽게 할 수 있는 장점이 있다. 모노레포 구조가 아니라면, 아마 대부분의 기업들은 우리가 늘상 써오던 멀티레포(폴리레포) 방식을 사용할 것 이다.

멀티레포(폴리레포)

멀티레포 구조는 폴리레포 구조라고도 불린다. 멀티레포 방식은 각 프로젝트나 패키지를 별도의 저장소에 관리하는 방식이다.

출처:https://monorepo.tools/

멀티레포에서는 다음과 같은 특징이 있다.

  1. 각 프로젝트나 패키지가 독립적인 저장소에서 관리되기 때문에, 프로젝트 간의 의존성이 최소화될 수 있다. 이로 인해 각 프로젝트마다 자율성이 높으며 독립적인 개발, 린트, 테스트, 빌드, 게시, 배포 파이프라인이 존재한다.
  2. 버전 관리: 각 프로젝트나 패키지가 독립적인 저장소에서 관리되므로, 버전 관리도 각각 수행된다. 이는 프로젝트별로 서로 다른 릴리스 주기와 버전 체계를 가질 수 있다는 장점이 있다.
  3. 멀티레포 방식을 사용했던 가장 큰 이유가 바로 자율성때문이었다. 팀은 어떤 라이브러리를 사용할지, 언제 배포할지, 누가 코드에 기여하거나 사용할 수 있는지 스스로 결정하기를 원했다.

그럼 멀티레포가 좋고 모노레포가 더 복잡하고 안좋은 방식이 아닌가? 하지만 큰 자율성에는 큰 책임이 따르는법. 아래와 같은 문제가 있었다.

  1. 코드 공유: 프로젝트 간에 코드를 공유하려면 별도의 패키지나 러이브러리를 만들어야 한다. 이는 개발 및 유지 보수 과정에서 추가적인 노력이 필요하다는 문제가 있다. CI 환경을 설정하고, 레포지토리에 커미터를 추가하고, 호환되지 않는 라이브러리 버전을 조정하고...
  2. 코드 중복: 각 레포지토리에서 공통적인 컴포넌트, 요소 등을 개발하며 심각한 코드 중복이 발생하게 된다. 이렇게 되면 초기 시간이 낭비되고 만약 컴포넌트와 요소들, 또한 서비스가 변경되면 각각에 대해 유지보수/관리 하는 부담이 증가하게 된다.
  3. 일관성 없는 도구: 각 프로젝트는 테스트 실행, 빌드, 서비스, 린팅, 배포 등을 위해 고유한 명령어를 사용해야 한다. 일관성이 없으면 프로젝트마다 어떤 명령을 사용할지 기억해야 하는 정신적 부담이 증가하게 된다.

이러한 문제점들을 극복하기 위해 모노레포 방식을 도입하는 기업들이 증가하게 되었다.

모노레포

출처:https://monorepo.tools/

모노레포로 다음과 같은 이점을 얻을 수 있다.

  1. 코드 공유와 재사용: 프로젝트 간에 공통된 코드를 쉽게 공유하고 재사용할 수 있다. 예를들어 많이 쓰이는 버튼, 폼, 각종 util들을 만들어 이를 재사용 함으로써 중복 코드를 줄이고 개발 효율성을 높일 수 있다.
  2. 의존성 관리: 모든 프로젝트가 하나의 저장소에서 관리되므로, 의존성 관리가 효율적이다. 이를 통해 프로젝트 간의 버전 호환성 문제를 최소화할 수 있다.
  3. 통합 테스트 및 CI/CD: 프로젝트 간의 통합 테스트와 CI/CD를 구성하는것이 더욱 편리하다. 이로 인해 빌드, 테스트 및 배포 프로세스를 효율적으로 관리할 수 있다.
  4. 협업 향상: 모노레포에서는 팀 구성원들이 전체 코드베이스를 쉽게 파알할 수 있으며, 프로젝트 간의 협업이 원활해질 수 있다. 또한, 모든 작업이 한 저장소에서 이뤄지기 때문에 코드리뷰와 변경사항 추적이 용이하다.
  5. 프로젝트 및 코드 파악 용이: 모든 코드가 하나의 저장소에 있기 때문에, 프로젝트 간의 레퍼런스 찾기와 검색이 용이해진다.

그럼 이 좋은 방식을 이전부터 사용하면 좋았을텐데 왜 최근에 들어서야 더욱 인기가 생긴걸까? 다음과 같은 원인들이 있었을 것이다.

  1. 도구 및 기술의 부족: 과거에는 모노레포를 관리하고 지원하는 도구와 기술이 제한적이었다. 하지만 현재는 Yarn, Lerna, Nx 등의 도구들이 모노레포를 지원하고 있어 쉽게 도입할 수 있게 되었다.
  2. 인식 및 경험 부족: 대규모 기업들이 모노레포를 성공적으로 사용한 사례가 잘 알려지지 않았으며, 이에 따라 모노레포의 장점과 효율성에 대한 인식이 부족했다. 최근에는 구글, 페이스북 등의 선구적 경험과 다양한 오픈 소스 프로젝트의 성공 사례가 전파되면서 인식이 바뀌었다.
  3. 개발 및 조직 문화: 과거에는 프로젝트별로 독립된 저장소를 사용하는 개발 및 조직 문화가 일반적이었다. 이로 인해 팀 간의 협업, 코드 공유, 통합 테스트 등이 제한되었다. 하지만 현재는 Agile, DevOps 등의 개발 및 조직 문화가 주류가 되면서, 모노레포를 통한 협업 및 지속적 통합/배포가 강조되고있는 분위기다.
  4. 확장성 및 관리 복잡성 우려: 모노레포는 초기에는 프로젝트 관리에 있어 복잡성과 확장성에 대한 우려가 있었다. 그러나 현재는 이러한 문제를 극복할 수 있는 도구와 관리 방법들이 출시되어 있어, 모노레포의 장점을 극대화할 수 있게 되었다.

멀티레포의 단점이 모노레포의 장점이고, 모노레포의 단점이 멀티레포의 장점으로 서로 교차하는 부분이 있다.

Yarn Workspace

모노레포 관리 도구로 Lerna, Turborepo, Nx 등 여러가지가 있는데 필자는 Yarn Workspace를 사용했다. workspace는 Yarn(1.x) 또는 npm(7.x) 패키지 매니저에서 제공하는 기능으로 workspace 필드를 사용하여 node_modules 디렉토리에 workspace에 대한 심볼릭 링크를 생성하도록 한다. 이를 통해 모노레포에서 여러 프로젝트나 패키지를 효율적으로 관리할 수 있도록 도와준다.

{
  "workspaces": ["packages/*"],
  ...
  ...
}

최상위 루트의 package.json파일에서 위와 같이 설정해준다.

만약 client라는 패키지에 common이라는 패키지를 의존하게 하려면 add 명령어를 사용하여 의존성을 추가해줄 수 있다.

yarn workspace client add common@1.0.0

모노레포에서는 eslint, prettier, typecheck 등 하나의 공통 설정/코드를 각 패키지에 적용하여 관리할 수 있다. typecheck 적용 예시를 한번 살펴보자.

명령을 실행할 각 패키지의 package.json에 typecheck 스크립트를 추가해준다.

// ex1) apps/clietnt/package.json
// ex2) packages/ui/package.json
// ex3) packages/common/package.json

"scripts": {
  "typecheck": "tsc --project ./tsconfig.json --noEmit"
},

yarn workspace에서는 패키지들을 관리하기 위한 workspace tools plugin을 제공해주는데 이를 설치해준다.

yarn plugin import workspace-tools

이제 root의 package.json에 아래 script를 추가해준다.

"scripts": {
  "g:typecheck": "yarn workspaces foreach -pv run typecheck"
},

workspace tools plugin 덕분에 yarn workspaces foreach 명령어와 함께 -p (병렬실행), -v (workspace name 출력) 옵션으로 실행시켜줄 수 있다.

각 패키지별 typecheck 실행 예시

Yarn Berry

Yarn Berry는 Yarn 패키지 관리자의 2.x, 3.x, 4.x 등의 버전으로 몇 가지 중요한 기능 및 개선사항이 추가되었는데 아래와 같이 버전을 set 할 수 있다.

Plug 'n' Play(PnP)

Yarn berry에서 가장 중요한 개념 중 하나가 바로 Plug 'n' Play(PnP)이다. 이는 기존의 node_modules 폴더를 사용하지 않는 대신,

프로젝트 의존성을 관리하는 새로운 방식을 제공한다. node_modules를 활용한 문제는 https://d2.naver.com/helloworld/7553804 에 자세히 정리되어 있어 한번 읽어보면 좋을것 같다. PnP 방식은 ./yarn/cache 폴더에 수평적으로 의존성을 저장하는 방식을 따른다.

간략하게 PnP의 특징과 함께 정리해보면,

  1. 디스크 공간 절약: 전통적인 node_modules 구조는 종종 중복 패키지를 포함하며, 이로 인해 많은 디스크 공간을 소비한다. PnP는 중복을 최소화하고 더 효율적인 패키지 저장 방식을 제공한다.
  2. 빠른 설치: 압축 파일 단위로 설치되기 때문에 의존성을 구성하는 파일의 수가 절대적으로 감소한다. 여기에 zero-install 전략을 사용하면 아예 설치 과정을 생략할 수 있다.
  3. 유령 의존성 방지: 호이스팅을 사용하지 않기 때문에 의도하지 않은 의존성이 발생하지 않는다.

PnP는 .pnp.js 또는 .pnp.cjs 파일을 생성하여 작동한다. 이 파일은 프로젝트의 의존성에 대한 메타정보를 포함하며, node.js가 모듈을 해석할 수 있도록 도와준다. 이를 통해 의존성을 효율적으로 관리할 수 있다.

 

zero-install

zero-install. 개발자로써는 이름만 들어도 가슴이 두근거리는 단어이다. 하나의 압축 파일로 의존성을 관리하고 이 파일을 git으로 관리하면 설치 과정을 제거할 수 있는데 이와 같은 전략을 zero-install이라 한다.

  • 다른 브랜치에서 새로운 의존성이 설치되었을 때 설치 과정 없이 바로 사용할 수 있다.
  • CI에서 의존성 설치에 드는 시간을 크게 줄일 수 있다.

zero-install 전략을 적용했을때 정말 zero-install인지. 설치과정이 대체 얼마나 빠른것인지 다른 설치 전략과 함께 비교하는 글을 추후에 작성해보려고 한다. zero-install에 대한 더 자세한 내용은 그때 다루도록 하겠다.

모노레포 CI/CD 관리

모노레포에서 CI/CD 배포 파이프라인의 속도와 리소스 낭비 방지를 위해 변경점이 있는 패키지에 대해서만 빌드하도록 해주는것이 중요하다.

당근마켓이나 Slash 저장소를 살펴보면 yarn-plugin-workspace-since 라는 yarn plugin을 사용하는것을 볼 수 있다.

이 plugin은 주어진 두 git revision 사이에 변경점이 있는 workspace에 대해서 주어진 명령어를 실행한다. "A" workspace에 의존성을 가진 "B" workspace가 있을때 "A", "B" 모두에 대해서 run이 실행된다. 변경된 workspace가 없다면 아무것도 실행하지 않는다.

$ yarn workspaces since run <command> <from> [to]

위 plugin을 아직 현업에 적용해보진 않았는데 이와 함께 적절하게 CI/CD 빌드 파이프라인을 관리해주면 속도와 리소스 낭비를 어느정도 방지해줄 수 있을것 같다.

정리

모노레포를 활용한 패키지 관리는 정말 매력적인 방식이지만, 그만큼 고려해야 할 사항들도 많고 각종 도구들의 장단점도 이해하고 있어야 하며, 패키지 의존성 관리, CI/CD 관리 등 생각해봐야 할 것들이 많기 때문에 정말 모노레포가 우리 회사에서 필요한지, 오히려 배보다 배꼽이 더 커지는 리소스 낭비가 발생하지는 않을지 충분히 고민해보고 활용을 해본다면 더 좋을것 같다.

 

 

참고자료

https://d2.naver.com/helloworld/7553804

https://d2.naver.com/helloworld/0923884

https://blog.hwahae.co.kr/all/tech/11962

https://channel.io/ko/blog/monorepo-in-operation