💻 Frontend

일백개 패키지 모노레포 우아하게 운영하기

category
💻 Frontend

모노레포 우아하게 운영하기

모노레포란?

“A monorepo is a single repository containing muliple distinct projects, with well defined relationships.”
“모노레포란 잘 정의된 관계를 가진, 여러개의 독립적인 프로젝트들이 있는 하나의 레포지토리이다.”
 

왜 모노레포를 사용해야 할까요?

멀티레포 대신 모노레포를 사용하는 이유가 뭘까?
일단 멀티레포란 우리가 알다시피 각각의 서비스가 각자의 레포지토리에 있는 것이다. 이러한 멀티레포 구조는 어떠한 문제점이 있을까?
출처 - FECONF 2022
출처 - FECONF 2022
프로젝트 A와 비슷한 형태의 프로젝트 B가 새로 추가되는 상황을 살펴보자.
  1. 새로운 Project B 레포지토리를 추가한다.
  1. ci/cd, lint, test, ts등등의 설정을 새로 해준다.
  1. Project A에서 재사용하고 싶은 코드가 있는 경우에는 이를 위한 Library A 레포지토리를 새로 만들어준다.
  1. 2 반복.
이러한 상황은 마인드웨이라는 프로젝트를 하면서 직접 겪어보았다. 해당 프로젝트는 관리자 페이지와 사용자 페이지를 멀티레포로 관리하였는데, 두 서비스가 디자인이 비슷하여 공통 UI들이 많았다. 그래서 코드를 복붙하는 일이 많았고, 이때 모노레포의 중요성을 뼈저리게 느꼈다.
 

멀티레포의 문제점

  • 새 프로젝트 생성 비용이 큼
  • 프로젝트 간 코드 공유가 어려움
  • 같은 이슈를 수정하기 위해 각각의 레포에 커밋이 필요
  • 히스토리 관리의 어려움
  • 제각각인 툴링으로 개발자 경험이 일관적이지 않음
 

모노레포가 해결해주는 것

notion image
  • 새 프로젝트 생성 비용이 적음
  • 프로젝트 간 코드 공유가 쉬움
  • Atomic Commits - 하나의 커밋으로 모든 레포 관리 가능
  • 히스토리 관리가 쉬움
  • 공통된 툴링으로 일관적인 개발자 경험 제공
 

Q. 그러면 모노레포가 무조건 좋은거 아님?

A: 그렇다고 모노레포가 무조건 정답인 것은 아니다! 앞에서 모노레포의 뜻을 보자면 “잘 정의된 관계”를 가진 여러 프로젝트를 담고있어야 한다. 이러한 프로젝트 간의 관계로 인하여 얻는 것이 클 때 사용해야 한다. 무작정 하나의 레포지토리에 모든 것을 담게 된다면 얻는 것보단 잃는게 클수도 있다.
 

우아하게 운영하기

앞서 계속 강조했던 모노레포에서의 “잘 정의된 관계”는 어떤 것일까? 잘 정의된 관계란 해당 모노레포만의 특징을 잘 이해하고 있다는 것이고 해당 모노레포에서만 요구되는 여러가지 요구사항들을 기술이나 제도적으로 모노레포를 이용해서 잘 풀수 있어야 한다.
 

토스에서는 모노레포를 어떤 식으로 사용할까?

토스 프론트엔드 챕터에서 공통으로 사용하는 내부 라이브러리 모노레포,
서비스 개발을 편하게 할 수 있도록 도와주는 크고 작은 100개 가량의 라이브러리들로 구성되어 있다고 한다.
 

라이브러리 모노레포의 특징

모노레포의 종류는 크게 나눌 수 있다.
  • Library Only Monorepo
  • Service Monorepo
최근 1개월 간 커밋 수
배포
영향범위 / 커밋
Library
80+
NPM
Service
700+
내부 인프라
작음
두 개의 큰 차이점은 커밋 수이다.
라이브러리에서 변경이 일어나게 된다면, 해당 라이브러리를 사용하는 서비스에 영향을 미칠 수 있다. 반면 서비스에서 변경이 일어나게 된다면, 한 커밋 당 하나의 서비스만 변경되기 때문에 영향범위가 작다.
 

라이브러리 모노레포이기에 더 중요하게 생각해야 하는 것들

 
  1. 의존성 관리
가장 많이 사용하는 패키지 매니저인 npm의 가장 큰 문제점은 유령 의존성이다.
notion image
중복적인 라이브러리 설치를 줄이기 위해 호이스팅 기법을 사용한다. dependency로 명시되지 않은 패키지를 불러올 수도 있다. 이러한 점은 의존성 시스템을 매우 혼란스럽게 하고, 최악의 상황에 경우 런타임에서 에러가 날 수도 있다.
 
해당 문제를 해결하기 위해 node_modules 말고 yarn berry와 pnp 방식을 사용하게 되었다. 그래서 패키지를 호이스팅 하지 않고 yarn .cache에 저장되며 설치된다.
이러한 의존성 설치는 .pnp.cjs를 통해 엄격하게 관리된다. 명시되어 있지 않은 의존성은 코드에 사용할 수 없다.
 
  • Zero install
  • 빠른 의존성 검색
 

What is Peer Dependency ?

상속된 dependency로, 패키지 자체가 아닌 패키지를 사용하는 곳에서 제공되어야 한다는 것.
 
notion image
이러한 상황은 두개의 B가 포함됨
notion image
하지만 B가 P의 peer dependency로 포함된다면, b를 a에게 제공하는 책임은 a를 사용하는 곳인 p 에 있다.
 
기존 의존성에선 b를 제공하는 책임이 a에도 있었고, 여기서는 차이점이 b를 제공하는 책임이 a가 아닌 p에 있다는 것이다.
따라서 p를 번들링하게 되면 p의 번들에는 하나의 b만 포함하게 된다.
 
그럼 모든 dependency를 peer dependency로 하면 되는거 아님?
그건 아니다.
peer dependecy는 전파된다. p에서 b를 peer dependency로 명시하고 있다면 b를 의존성으로 갖고 있는 모든 패키지들에서 b를 코드에서 사용하지 않더라도 b를 peer dependency로 추가해줘야 한다.
 
peer dependency는 언제 써야 되는거임?
싱글턴으로 존재해야하는 패키지일때만 peer dependency로 명시해주자.
우리가 자주 사용하는 react나 next 등은 한 프로젝트 내에서 꼭 하나의 싱글턴으로 관리되어야 한다. 이러한 패키지를 바로 peer dependency로 명시하면 된다.
 
 
 
  1. 버전 관리
라이브러리에는 특히 버전 관리가 중요하다. 노드 생태계에서 흔히 사용되는 버전의 시스템은 셈버라고 불리는 시스템이 있고, 토스도 셈버를 기반으로 버전 관리를 한다.
 

그럼 셈버가 뭐임?

Semantic Versioning의 줄임말, 버전 넘버가 어떻게 할당되고 증가되는지에 대한 일종의 명시적인 규칙과 요구사항이다.
셈버는 세자리 숫자(ex. 5.7.8)로 이루어져 있다.
첫번째 숫자
  • 메이저 버전
  • API 등이 변경되어 브레이킹 체인지가 일어날 때 버전 변경이 일어남 ↔ 반대로 메이저 버전의 숫자가 변경이 되지 않았다면 브레이킹 체이지가 없다는 말(하위 버전이 변경되었더라도 빌드에 영향 가지 않다는 것을 알 수 있음)
두번째 숫자
  • 마이너 버전
  • API 인터페이스 등이 변경되지는 않지만 새로운 기능이 추가될 때 버전 변경이 일어남
세번째 숫자
  • 패치 버전
  • 사소한 버그, 새로운 기능 추가가 아닌 기존의 동작을 수정하는 그런 패치 형식의 변화가 있을 때 버전 변경이 일어남
 
라이브러리를 사용하면 코드와 변경 히스토리를 모두 알고 사용하는 것이 아니다. 사용하는 곳에서는 오로지 셈버에만 의존하여 의존성을 관리하게 된다. 이는 라이브러리와 이를 사용하는 것과는 유일한 약속이고 이렇기 때문에 라이브러리 모노레포에서는 버전을 더욱 엄격하게 지켜야 한다.
 
lerna version
그래서 토스는 러너라는 모노페토 풀을 사용하고 있다. 러너를 이용하여 변경된 커밋에 영향을 받는 패키지들의 셈버 버전을 엄격하게 올려주고 또 배포하는 역할을 한다.
 
  1. 코드 품질 관리
RFC(Request For Comments)
PR은 실제 코드를 구현해야만 올릴 수 있는 방면에, RFC는 PR을 올리기 전 아이디어 만으로 라이브러리에 추가하거나 기여하고 싶은 부분이 있을 때, RFC를 올려 설계에 대한 리뷰와 피드백을 받을 수 있다.
 
이러한 과정을 통해 라이브러리 설계가 한층 더 탄탄해지고 코드 품질 또한 올라가게 된다.
 
PR(Pull Request)
토스에서 pr은 코드오너의 승인과 ci를 통한 코드검증이 통과해야만 merge될 수 있고, 이러한 제약은 코드 품질을 한층 더 올릴 수 있게 된다.
  • peer dependency 에러가 있는가?
  • 사용하지 않는데 export되고 있는가?
  • 배포 전 빌드가 잘 되는가?
  • 카나리 배포가 잘 되는가?
 
  1. 문서화
라이브러리인만큼 어떻게 사용하는지에 대한 문서가 없다면 사용에 어려움이 있게 된다. jsdoc과 도쿄사우루스를 이용하여 문서화를 구축할 수 있을 것이다.
이러한 문서화 시스템 구축에 있어서도 모노레포만의 장점이 있다. 각각의 레포마다 문서의 시스템을 구축하고 이를 연동하려면 큰 비용이 들 것이다. 하지만 모노레포 이기 때문에 하나의 시스템 안에서 하나의 config만으로 문서화를 할 수 있다.
 

마무리

해당 영상을 보고 모노레포가 어떤 것인지 잘 알게 되었다. 다만 아쉬운 점은 어떻게 구축했는지 실습 코드를 알 수 없었던 것이다.
가장 기억에 남는 내용은 무조건 모노레포가 좋은 게 아니라 프로젝트의 요구사항과 특징을 바탕으로 구축을 고려해야 한다는 것이다. 예를 들어 관리자&사용자 서비스가 나뉘어 있거나 여러 서비스가 한 디자인 시스템을 공유하는 프로젝트면 모노레포로 구축하는게 좋을 것이고, 그게 아니라 작은 규모의 서비스나 요구사항이 복잡하지 않는 서비스라면 모노레포로 구축했을 시 복잡함이 커질 수 있다.

References