Monorepo로 대규모 React 프로젝트 관리하기

Sungdong Jo
How we build MyRealTrip
9 min readMay 20, 2020

시작하며

마이리얼트립의 서비스는 그 규모가 점점 커지며 복잡성도 증가하고 있습니다. 이때 React로 개발하고 있는 프론트엔드 영역에서 Monorepo를 도입하여 MSA를 대응하고 대규모의 React 프로젝트도 관리 가능한 상태로 만들어나가는 과정을 공유합니다.

MSA?

MSA(Micro-service Architecture)는 개별 배포가 가능한 각자의 기능을 가진 서비스들이 한데 모여 커다란 하나의 시스템을 구성하는 것을 일컫습니다. 현재 개발팀에서는 MSA로 전환하기 위해 노력하고 있습니다. 아래의 글에서 상세한 내용을 보실 수 있습니다.

마이리얼트립의 MSA 전환

repo : 저장소를 의미하는 repository의 준말
Multirepo : 복수의 저장소에 각각의 프로젝트를 두어 분산시키는 구조
Monorepo: 하나의 저장소에 여러 프로젝트를 두는 구조
workspace: Monorepo에서 각각의 프로젝트 또는 패키지
vertical: 마이리얼트립의 상품 카테고리

References

문제 인식

MSA를 대응하기 위한 준비

전사적으로 MSA(Micro Service Architecture)를 지향하고 있지만, 현재는 하나의 entry가 존재하고 사이트 전체를 빌드하는 환경이었습니다. 따라서 이를 분리하여 각각의 패키지(vertical)의 빌드가 가능한 환경이 필요했습니다.

Multirepo 시 환경구성 중복과 이슈 분산

프로젝트마다 repo로 분리하여 개발하는 것이 일반적일 수 있습니다. 하지만 Multirepo로 개발 시엔 새로운 repo가 만들어질 때마다 기존의 환경구성과 모듈을 재사용할 수 있음에 불구하고 코드를 복사 / 붙여넣기 해야 하는 문제가 예상되었습니다. 또한, 개발 시에 생기는 이슈도 repo로 분리되어 있으므로 이슈도 여러 repo에서 함께 관리해야 하는 번거로움도 같이 수반되었습니다.

코드베이스의 복잡성 증가

// As is./pages
/page1
/page2
a lots of pages...
components/
/component1
/component2
/component3
/childComponent
...
a lots of components...
...
more and more complex...! 🙄

일반적인 React 구조에서는 componentspages로 분리하고 페이지 단위로 개발하게 되는데 페이지수와 컴포넌트 수가 계속해서 증가하며 수정이 필요한 컴포넌트나 모듈들을 찾기 파악하는 데도 더 노력을 들여야 했습니다. 개선하기 위해 단순히 디렉터리를 구조를 변경하는 방법을 택할 수도 있지만, 이 또한 디렉터리 depth를 증가시키는 복잡성을 가져오기 때문에 본질적인 해결방법은 아니었습니다.

Monorepo 기반으로 나아가기

Yarn workspace? Lerna?

Code sharing: Lerna(O) / Yarn workspace (O)

Versioning : Lerna(O) / Yarn workspace (가능하나, 수동으로 가능)

Utilities : Lerna(High) / Yarn workspace (Low)

우선 Monorepo 환경을 구축하기 위해서 Yarn workspaceLerna를 사용할 수 있습니다. 둘 중 Monorepo와 관련된 여러 기능(버전관리 등)들을 활용하고 싶은 경우엔 Lerna를, 기본적인 Monorepo 환경 구성만을 원한다면 Yarn workspace를 선택하여 사용할 수 있습니다. 아직은 Lerna의 다양한 기능들을 활용할 일이 적다고 판단되고 처음 도입하는 만큼 lean 하게 시작하기 위해서 Yarn workspace만 활용했습니다.

패키지 간 참조로 모듈의 재사용

패키지간 참조
page1/
package.json
page2/
package.json
component1/
package.json
utils/
module1
package.json
> yarn workspace page1 add utils

각 페이지에서 공통으로 사용하는 부분들은 개별 workspace로 분리하고 아래와 같이 참조를 추가해주면 패키지 간 재사용이 1:1 또는 1:N으로 가능해집니다. 따라서 페이지에서 필요한 패키지들을 모두 참조하여 사용할 수 있습니다.

prefix로 “mrt”를 붙인 이유는 utils라는 이름의 패키지가 npm에 이미 등록되어 있다면 참조하고자하는 로컬 workspace가 아니라 npm의 패키지를 불러올 수 있기 때문입니다.

패키지 간 일관된 환경 공유

/lodging
webpack.config.js // root webpack을 참조
...
/tour-ticket
webpack.config.js
...
/webpack // root webpack
webpack.config.js
webpack.common.js
webpack.dev.js
webpack.prod.js
... more env files

workspace로 분리함에 따라 각각의 패키지의 배포가 하나의 스크립트로 가능해졌습니다. 하나의 버티컬을 빌드 또는 배포하고 싶다면 아래의 스크립트만 실행하면 가능해집니다.

yarn workspace ${패키지명} build // or
yarn workspace ${패키지명} deploy
webpack build flow

빌드 과정은 위와 같이 수행되며 모든 버티컬에서 root의 빌드 환경을 참조하여 사용하는 구조가 갖춰지게 됩니다. 결과로써 모든 패키지에서 일관성 있는 빌드를 기대할 수 있고 중복으로 환경을 구성하는 비용을 줄일 수 있었습니다.

전체 빌드 시에는 빌드 속도가 느릴 수 있습니다. 경우에 따라 entry 별 병렬 빌드를 지원하는 도구를 사용할 수도 있습니다.
https://webpack.js.org/guides/build-performance/#multiple-compilations

버티컬별 workspace 분리를 통해 복잡성 낮추기

점점 증가하는 코드 베이스를 관리하기 위해 다른 오픈소스들은 어떻게 하고 있을까요? 대표적으로 facebook의 React는 Yarn workspace 기반의 Monorepo 구성을 도입하여 각각의 기능들을 패키지로 나누어 관리하고 있습니다.

마이리얼트립 서비스는 투어&티켓, 패키지, 숙소 등을 “버티컬”이라는 용어로 부르고 있습니다. 또한, 개발 시에도 여러 버티컬에 걸쳐서 개발하기보다는 특정 버티컬에서 개발하는 상황이 대부분입니다.

마이리얼트립의 버티컬들

이 버티컬 개념을 프로젝트에 반영하여 workspace도 이 기준으로 나누었습니다. 따라서 개발 시에 다른 버티컬은 분리되어 있으니 해당 버티컬에서 사용되는 컴포넌트와 모듈들을 파악하기 쉬워졌으며 개발하고자 하는 영역에 집중할 수 있었습니다.

/packages
/lodging
/pages
...pages
/components
...components
/hooks
...hooks
...envfiles
webpack.config.js
/tour-ticket
/pages
...pages
/components
...components
/hooks
...hooks
webpack.config.js
// common workspaces...

덧붙여 공통으로 사용하는 모듈들은 다른 (공통으로 사용하는)workspace를 참조하거나 외부의 패키지를 불러와 사용할 수 있었습니다.

독립적인 의존성

A라는 패키지에서 사용하지만, B라는 패키지에서는 사용하지 않는 라이브러리가 있다면 어떻게 될까요? 보통은 B를 빌드할 때도 같이 빌드되겠지만 Monorepo에서는 의존성을 독립적으로 가지기 때문에 각 패키지에 필요한 의존성만 가지고 빌드가 가능해집니다. 또, 상황에 따라 모든 패키지에서 같은 의존성을 가져야 한다면 hoist 방식으로 추가할 수도 있습니다.

/packages
pageA/
package.json => { dependencies: { "libraryA" : "^1.0.0" } }
pageB/
package.json => { dependencies: { "libraryA" : "^2.0.0" } }
// 개별 workspace에 독립적으로 추가하는 방식
yarn workspace pageA package-name@1.2.3
// hoist 방식으로 추가하는 방식, 전체 workspace에 영향을 주며 workspace에서 따로 추가하지 않는다면 root의 의존성에서 탐색함
yarn add package-name@2.3.4 -W

이는 기존에 작성된 코드도 동작하는 상태로 공존하면서 새로운 스펙을 도입한 버티컬을 개발할 수 있다는 점을 의미합니다.

마치면서

기존의 비대해진 React 프로젝트에 Monorepo 도입은 쉬운 것만은 아니었습니다. 의존성을 잘 분리해내는 것과 여러 가지 환경구성을 다시 재배치 해야하는 작업이 필요합니다. 다만 한 가지 분명한 것은 잘 구축해놓은 Monorepo는 비대해지는 프로젝트를 관리 가능한 상태가 되도록 도와준다는 것입니다.

MSA를 지향하는 상황에서 규모가 커지는 React 프로젝트를 관리하는 방법으로 Monorepo 도입도 좋은 선택지라고 생각합니다.

마이리얼트립 프론트엔드팀이 문제를 해결하고 개선해나가는 과정에 관해 작성해보았습니다. 팀과 회사에 대해 궁금하신 분들은 아래의 페이지를 방문해주세요.

https://career.myrealtrip.com/

--

--