우리는 프로젝트가 작을 때는 하나의 레포지토리로 프로젝트를 관리하지만, 커지면 커질수록 하나에서 관리할 지 나누어 관리할 지를 정하게 되는데, 프로젝트 내부의 개별 레포지토리에서 관리할 것인지, 하나의 레포지토리에서 관리할 것인지에 따라 이름이 달라집니다
하나의 레포지토리에서 관리하는 것을 MONOREPO, 개별 레포지토리에서 관리하는 것을 MULTIREPO라고 정의합니다
MONOREPO | MULTIREPO | |
---|---|---|
장점 | 지속적인 소스의 무결성 보장 : 레포지토리는 항상 모든 서비스가 연동된 올바른 상태를 유지한다 | 강한 오너쉽 확보 : 레포지토리 별로 오너를 지정할 수 있다 |
통합된 버전 관리 : 모든 서비스가 연동된 상태에서 손쉽게 하나의 버전으로 관리가 가능하다 | 마스터 코드가 깨질 여지가 적다 : 코드 베이스가 아예 나뉘어 있고, 서로간의 작업 충돌로 마스터 코드가 깨질 가능성이 적다 | |
코드의 공유와 재사용이 용이 : 소스 단위의 연동이 이루어진 상태 | 형상 관리, CI 속도가 빠르다 : 레포지코리의 크기가 작기 때문에, 레포지토리 훅을 기반으로 동작하는 도구들의 속도가 빨라진다 | |
의존성 관리가 쉽고, 원자 단위 변화 : 전체 서비스의 의존 관계가 한 레포지토리에서 확인 및 설정이 가능하고, 변화가 여러 스텝이 아니라 한 레포지토리에서 한 스텝으로 이루어진다 | ||
여러 프로젝트팀 간의 협업이 쉬움 :하나의 레포지토리에서 함께 작업하며, 여러 서비스에 손쉽게 접근 가능하다 | ||
유연한 팀 바운더리 설정과 코드 오너쉽을 가져갈 수 있음 :하나의 레포지토리, 하나의 서비스에 제한된 코드 오너쉽을 유지하지 않아도 됨 | ||
통합 CI 및 테스트 : 모든 소스가 연동된 상태. CI 구성이 손쉬움 | ||
전체 코드가 트리 구조로 명확히 보임 | ||
한 번의 코드 리뷰에 모든 변화가 요약 |
단점을 살펴보자면,
MONOREPO | MULTIREPO | |
---|---|---|
단점 | 무분별한 의존성 연결 가능 : 의존성 연결이 쉽기 때문에 오히려 과도한 의존 관계가 나타날 수 있음 | 코드 재사용이 쉽지 않으므로, 중복 코드 가능성이 높아지고 다른 레포지토리의 코드를 사용하기 위해서 해야 할 작업들이 존재한다 |
형상 관리 및 CI 속도 저하 : 레포지토리 크기가 크기 때문에 레포지토리 훅을 기반으로 동작하는 도구들의 속도가 느려짐 | 하나의 피쳐 개발을 위해 여러 레포지토리에 머지를 해야함 | |
코드 리뷰가 나누어진다 | ||
버전 연동이 깨질 위험이 있다 : 하나의 브레이킹 체인지가 다른 레포지토리로 즉시 전파되지 않는다) | ||
디펜던시 헬 : 프로젝트가 거대화 됨에 따라 의존 그래프가 매우 복잡해지게 되고, 서로의 코드에 변화가 생길 때 이를 대처하기 쉽지 않다 |
참고 사이트:https://tech.buzzvil.com/handbook/multirepo-vs-monorepo/
$ npm install turbo --global
{
"name": "project-name", // 중요!!
"version": "0.0.0",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"prepare": "husky install"
},
"devDependencies": {
"eslint-config-custom": "*",
"husky": "^8.0.3",
"lint-staged": "^13.1.1",
"turbo": "latest"
},
"engines": {
"node": ">=14.0.0"
},
"packageManager": "npm@8.6.0",
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",
"*.css": "stylelint —fix"
},
"dependencies": {
"react-loading": "^2.0.3"
}
}
// before 예:
"scripts": {
"deploy:storybook": "yarn lint && yarn uvp-core build && yarn uvp build && yarn storybook build && yarn test && yarn storybook deploy"
}
// after 예:
"scripts": {
"deploy:storybook": "turbo run deploy --scope='storybook'"
}
{
"baseBranch": "origin/main",
"pipeline": {
// 스크립트와 매핑되는 태스크 이름을 작성합니다.
"build": {
// 의존성 빌드 명령이 실행된 후 build 커맨드가 실행됩니다.
"dependsOn": ["^build"],
// 기본 캐시 폴더를 지정합니다.
"outputs": [".next/**", "lib/**", "storybook-static/**"]
},
"snapshots": {
"dependsOn": ["@linecorp/uvp#build"]
},
"lint": {},
"deploy": {
// 의존성을 여러 개 지정할 경우 터보가 똑똑하게 순서를 맞춰서 진행합니다.
"dependsOn": ["build", "cypress:ci", "snapshots", "lint"]
},
"profile": {
"dependsOn": ["deploy"]
},
"dev": {
"dependsOn": ["@linecorp/uvp#build"],
"cache": false
},
"clean": {
"cache": false
}
}
}
// package.json
"scripts": {
"snapshot": "turbo run snapshots",
"build": "turbo run build",
// --scope는 build를 실행할 패키지 범위를 지정합니다. --no-deps와 --include-dependencies를 함께 사용하면 해당 스크립트에 필요한 의존성과 함께 실행합니다.
"build-uvp": "turbo run build --scope='@linecorp/uvp' --no-deps --include-dependencies",
// 이와 같이 run 다음에 태스크를 나열하면 각 작업의 우선순위에 따라 터보가 자동으로 정렬해 실행합니다.
"test": "turbo run build lint cypress:ci snapshots",
// --force 옵션을 넣으면 캐시된 작업을 다시 실행합니다. --profile, --graph 옵션은 아래에서 다시 다루겠습니다.
"profile": "turbo run profile --profile --force && turbo run profile --graph",
"clean": "turbo run clean && rm -rf node_modules"
}
사실 세팅은 나도 아직 미흡한 부분이 많고, 실제 서비스에 적용시켜본 적은 없지만 현재 프로젝트에서 유용하게 사용하고 있다
모노레포를 사용하는 프로젝트에서 라이브러리를 설치하고 싶을 때,
혹은 모노레포를 사용하는 프로젝트를 clone클론 받았을 때, 프로젝트에 설치된 라이브러리 한 번에 설치하고 싶을 때 맨 처음 지정해주었던 project-name
을 사용해야 한다
$ npm install react-cookie --workspace=project-name
// 와 같이 사용
// workspace 이름은 package.json 파일에 있는 name으로 지정해주어야 한다
$ npm i --workspace=project-name
// 다른 사람이 추가한 라이브러리를 추가로 설치할 때도 마찬가지다