참고 자료
콴다 프론트엔드 팀이 모노레포를 선택한 이유 🎠
모노레포 도입 배경
기존 멀티 레포(Multi Repo) 방식
- 각 도메인/기능 별로 40개가 넘는 레포를 관리
장점
- 각 프로젝트가 고유의 저장소를 가짐
- 다른 프로젝트와의 의존성이 없고 독립적으로 빠른 개발 가능
- 크기가 가벼워 관리 면에서 수월
문제점
시간이 가면서 프로젝트 갯수가 많아지면서 다음 문제 발생
- 각 프로젝트(= 레포지토리)의 코드 컨벤션 통일이 어려워짐
- 업데이트를 잊을 경우 프로젝트별로 사용하는 모듈이나 버전 스택이 달라짐
- 오랫동안 건드리지 않은 레거시 프로젝트의 관리 - 시간이 지날수록 해당 프로젝트 파악이 어려워짐
- 팀원별 컨텍스트 공유가 원활하지 않게 됨 (각 팀원이 동일한 문제를 겪어도 서로의 컨텍스트가 공유되지 않아, 각자 시간을 들여 다양한 방식으로 문제를 해결)
- 새로운 서비스 기능 추가로 새 레포를 생성할 때마다 Lint 환경을 반복적으로 세팅해야 하는 불편함
=> 장기적인 관점에서 다른 방법을 시도하게 됨 (바로 모노 레포를 쓰기 시작한 것은 아니고 몇 단계를 거침)
벗어나기 위한 노력
1. 공용으로 사용 가능한 BoilerPlate 레포 구축
- 신규 프로젝트(레포) 생성 시 바로 가져와서 쓸 수 있는 BoilerPlate를 레포로 구축
=> 필요한 Lint 환경과 SSR/CSR 환경이 기본 셋팅된 Template 레포를 새 레포 생성 때 가져다 쓰게 함
문제점
- BoilerPlate가 레거시 레포가 됨
- 별도의 Owner 지정이 없어서 관리 책임이 명확하지 않음 + BoilerPlate의 환경 설정과 모듈 버전 최신화 등의 관리 작업 필요
- 팀원들이 평소에는 잘 들여다보지 않는 레포 => 관리가 되지 않음
2. 컨벤션을 통일하는 모듈 구축 및 설치
- eslint 설정 관련 Dependency 모듈과 Configuration 코드를 하나의 설치 가능한 모듈로 개발 후 신규 프로젝트에 설치해서 사용하도록 함 (통일된 Lint 환경 이용을 위해)
사용 예: devDependencies에 @mathpresso/eslint-config
를 설치
문제점
- 시간이 지날수록 프로젝트별 업데이트 여부의 차이
- 같은 모듈이지만 다른 버전을 사용하는 경우 서로 다른 Lint 컨벤션을 사용하게 됨
- 또한 신규 프로젝트를 생성할 때마다 해당 모듈을 설치해줘야 하는데 이를 누락할 가능성
3. 회의를 통한 정기적인 컨텍스트 공유
- 주 1회 정기적으로 컨텍스트를 공유하여 각자 맡은 프로젝트 환경을 오프라인 차원에서 맞추고자 함
문제점
- 회의 시간이 점차 길어져 업무에 지장을 준다는 의견
==> 최종적으로 미팅 & 모듈 & 템플릿 없이도 모든 서비스를 하나의 레포에서 관리할 수 있게 하는 모노레포(Monorepo) 시스템 환경 구축
모노레포 구축: Yarn Workspace
- 팀이 Yarn을 사용하고 있어서 Yarn에서 제공되는 기능인 Yarn Workspace를 활용
기본적으로 생성되는 디렉토리 구조
이미지 원본 출처
- 최상단 root package.json은 모노레포 전체의 Dependency와 workspace에 대한 정보를 가짐
- 하위 디렉토리(각 프로젝트)의 package.json은 해당 프로젝트의 Dependency 관리
Yarn workspace 특장점
1. 모듈 호이스팅
- 각 프로젝트마다 공통적으로 가지는 모듈을 최상단 node_modules에 '호이스팅'
- 모든 프로젝트가 공통으로 최상단의 모듈을 사용 가능하기 때문에, 여러 곳에서 모듈이 중복으로 설치될 필요가 없음
- 장점: 중복되는 모듈 설치를 줄이고 전체적인 Dependency 사이즈 감소
기능의 이름은 '호이스팅'인데 실제 개념적으로는 자바스크립트의 Prototype과 비슷한 것 같다. 동작하는 모습은 '최상단으로 끌어올리는' 호이스팅과 비슷하지만, 실제로는 상위 인스턴스에 공통된 상태나 메서드를 두고 이를 상속해서 쓰는 Prototype 개념이 떠올랐다.
빌드 문제점
모노레포에서 각 프로젝트를 build할 때 아래와 같은 에러 메시지가 자주 뜸
- can’t find module “B@2.0” from project root “monorepo” (not able to follow symlink)
- can’t find module “A@1.0” from “package-1” (unaware of the module tree above in “monorepo”)
각 하위 프로젝트 build를 시도할 때, 모듈이 해당 로컬 프로젝트의 node_modules만 참조하려고 하면 root node_modules로 호이스팅 된 모듈은 로컬 node_modules에는 실제로 존재하지 않기 때문에 not Found 에러가 발생
해결: noHoist 옵션
yarn 공식문서에도 이러한 Can’t Found 문제는 쉽게 해결할 수 있는 문제가 아니라고 설명하며 호이스팅이 되지 않도록 하는 noHoist 옵션을 별도 제공
"workspaces": {
"nohoist": ["react-native", "react-native/**"]
}
noHoist를 이용하면 각 프로젝트의 의존성 모듈을 호이스팅하지 않고 프로젝트 내의 각 로컬 node_modules에 의존하여 설치되기 때문에 이 문제를 해결
2. 심볼릭 링크
- 라이브러리 형식의 프로젝트를 외부 프로젝트 코드처럼 바로 import 하여 사용 가능하게 하는 기능
예시
디자인 시스템: libraries/qanda-design-system
{
"name": "qanda-design-system",
"version": "1.0.1",
"dependencies": {
...
}
}
디자인 시스템을 services/project-a에서 모듈로 설치해서 사용
{
"name": "projectA",
"dependencies": {
"qanda-design-system": "1.0.1"
}
}
-
모노레포 내의 다른 프로젝트를 Dependency로 설정하여, 해당 프로젝트를 '심볼링 링크'로 참조함으로써 Dependency 모듈처럼 사용
-
빌드할 때도 심볼릭 링크로 참조되는 라이브러리 프로젝트부터 빌드가 됨
3. 유연한 Lint 셋팅 관리
- 모노레포 최상단에 lint 환경 설정 가능
- 특정 프로젝트에만 lint 규칙 변경 사항이 있을 경우 해당 프로젝트 내에 .eslintrc 파일을 따로 두고 관리
...
├─ services
│ ....
│ ├─ service-a
│ │ ├─ .eslintrc
│ └─ services-b
│ └─ .eslintrc
...
└─ .eslintrc
- 더 이상 프로젝트마다 Lint 셋팅을 위해 BoilerPlate를 만들거나 모듈을 설치하는 등의 관리를 안 해도 됨
git flow 작업의 변화
기존 git flow 방식: develop 브랜치 사용
- Default Branch인 develop과 master가 있고, develop 하위로 feature 브랜치를 따서 작업
- develop에 feature 브랜치가 merge가 되면 develop 환경에 CI/CD를 통한 배포하고, develop에서 최상위 master로 merge가 되면 production 환경으로 릴리즈하는 방식
모노레포 도입 후에는
-
기존의 CI/CD 배포 방식을 그대로 이용하기가 어려워짐
-
여러 서비스를 한 군데서 관리하면서, develop 브랜치에 merge가 되었을 때 어떤 서비스의 CI가 돌고 배포가 되어야 하는지 기준을 잡을 수 없게 됨
-
모노레포에 더 알맞은 새로운 브랜치 전략이 필요하다고 느껴서 Trunk based Development 전략을 도입
새로운 git flow 방식: Trunk based Development 전략
이미지 원본 링크
-
항상 릴리즈가 가능한 상태인 trunk(master) 브랜치가 있고, 바로 하위에 Feature 브랜치를 따서 작업 진행
-
master 브랜치의 바로 하위로 작업이 진행되는 만큼 위험
-
팀 내에서 지켜야 할 Rule 설정
- 작은 단위로 feature 작업 진행
- 주기적인 CI/CD 진행 (빌드 문제 발생하지 않도록)
결과
모든 개발자가 최상위 브랜치에 집중할 수 있게 되면서 컨텍스트 공유 및 여러 프로젝트의 align 맞추기가 수월해짐
모노레포로 인한 개선점 3가지
1. 코드 리뷰 참여도 증가 => 컨텍스트 공유가 수월해짐
- 각 프로젝트의 작업 사항을 하나의 Repository에서 한눈에 확인 가능
- 자연스레 다른 프로젝트의 코드를 들여다보며 코드 리뷰에 적극 참여
- 평소 내가 접하지 않던 프로젝트의 컨텍스트를 접할 수 있는 환경
소감
- 여러 프로젝트들의 PR들이 많이 올라와서 오히려 복잡해지지 않을까 걱정
- 하지만 PR이 올라오는 즉시 모든 멤버들이 코드 리뷰를 하고 피드백을 바로 반영하여 merge까지 진행됨
- 생각보다 복잡한 PR 리스트가 쌓이지 않고 오히려 더욱 빠른 피드백과 리뷰 반영 시스템이 갖추어짐
2. 최신화 상태 유지 용이 => 프로젝트 레거시 감소
-
기존의 멀티레포 환경에서는 수많은 프로젝트로 인해 오래 건드리지 않은 프로젝트의 레거시화 진행
-
오랫동안 작업 사항이 없어 건드리지 않다가 최근에 새롭게 작업할 사항이 생겼을 때, 현재와는 너무나 달라진 레거시 코드로 실제 기능 추가 작업보다 최신화 상태로 개선시키기 위한 작업으로 리소스 낭비
-
모노레포 도입 후에는 오랫동안 건드리지 않는 프로젝트도 주기적으로 함께 관리할 수 있는 환경 제공 => 모든 웹 프로젝트를 최신화 상태로 보존할 수 있게 됨
3. 손쉬운 Lint 컨벤션 통일 => 업무 능률 향상
- 각 프로젝트별 eslint와 prettier의 환경이 모노레포로 통일됨
- 모든 팀원이 동일한 코드의 컨벤션을 가지는 환경 마련
- 더 이상 Lint 환경 및 설정에 대해 신경 쓰지 않게 됨
- 실제 코드 작업에 더욱 집중 가능
- 프로젝트에서 추가로 필요한 설정/환경이 생기면 다같이 의논한 뒤에 모노레포에 반영 => 팀 협업을 좌우하는 컨벤션이 더욱 효율적으로 & 신중하게 적용됨