모노레포(Monorepo) 아티클 정리: 콴다

Sheryl Yun·2023년 4월 23일
1

참고 자료

콴다 프론트엔드 팀이 모노레포를 선택한 이유 🎠

모노레포 도입 배경

기존 멀티 레포(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 환경 및 설정에 대해 신경 쓰지 않게 됨
  • 실제 코드 작업에 더욱 집중 가능
  • 프로젝트에서 추가로 필요한 설정/환경이 생기면 다같이 의논한 뒤에 모노레포에 반영 => 팀 협업을 좌우하는 컨벤션이 더욱 효율적으로 & 신중하게 적용됨
profile
데이터 분석가 준비 중입니다 (티스토리에 기록: https://cherylog.tistory.com/)

0개의 댓글