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

프론트엔드에서의 Monorepo 도입기

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

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

MSA?

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

마이리얼트립의 MSA 전환

MSA(Micro Service Architecture)로의 전환

medium.com

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.jsonpage2/
package.jsoncomponent1/
package.jsonutils/
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

빌드 과정은 위와 같이 수행되며 모든 버티컬에서 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/

Read more

AI 추천과 예약 사이의 거리를 좁히는 법: 마이리얼트립이 조코딩 AX 해커톤에 낸 문제

AI 추천과 예약 사이의 거리를 좁히는 법: 마이리얼트립이 조코딩 AX 해커톤에 낸 문제

마이리얼트립은 조코딩AX파트너스가 연 해커톤 「AX 인재전쟁」에 기업 출제자로 참여했습니다. API를 제공하는 데서 나아가, 참가자들이 풀어야 할 문제를 직접 내고 심사했습니다. 문제의식은 단순합니다. AI를 여행에 흔히 쓰게 되면서 정보 탐색은 한결 쉬워졌는데, 정작 사람들은 여전히 여행 계획을 손으로 짭니다. 추천은 넘치는데 '진짜 갈 수 있는 경험'까지 좁혀주는

By 마이리얼트립
가장 중요한 문제에, 가장 잘 맞는 사람을 붙인다

가장 중요한 문제에, 가장 잘 맞는 사람을 붙인다

마이리얼트립의 프로덕트 엔지니어(PE)는 CTO 직속 PS팀(Product Strategy)에 모여 있습니다. 전사에서 가장 중요한 문제에 가장 잘 맞는 PE를 배치하고 한 명의 PE가 문제 정의부터 구현과 배포까지 끝까지 책임지는 조직입니다. 이렇게 한 데는 이유가 있습니다. 회사에서 가장 중요한 문제는 계속 바뀌는데, 사람이 특정 버티컬에 묶여 있으면 그 경계

By 마이리얼트립
돌려쓰던 테스트 환경을, 각자의 환경으로: 배포 병목을 없앤 이야기

돌려쓰던 테스트 환경을, 각자의 환경으로: 배포 병목을 없앤 이야기

PEPE 세션 — 회원주문개발팀 정현수 님 과거 마이리얼트립의 공용 테스트 환경은 쓰려는 사람에 비해 모자랐습니다. 누가 쓰는지 알기 어려워 "지금 써도 되냐"고 서로 물어 가며 차례를 기다리는 경우가 많았고, 내가 작업 중인 사이 다른 사람이 배포하지 않도록 조율해야 했습니다. 이 병목을 환경 개수를 늘리는 방식에서 각자 자기 환경을

By 마이리얼트립
예약에서 끝나지 않는 여행: 한 명이 10여 개 레포를 가로질러 만든 항공 여정 케어

예약에서 끝나지 않는 여행: 한 명이 10여 개 레포를 가로질러 만든 항공 여정 케어

마이리얼트립이 예약한 항공편의 운항 상태를 고객이 앱을 열지 않아도 실시간으로 받아보는 서비스를 선보였습니다. 출발 3시간 전 알림부터 게이트 변경과 지연, 결항, 회항, 출발, 도착, 수하물 상태까지, 비행의 흐름 전체를 고객이 찾기 전에 먼저 안내합니다. 여행은 항공권을 예약하는 순간 끝나지 않습니다. 짐을 싸고 공항으로 향하고 비행기에 올라 목적지에 내려 짐을 찾을

By 마이리얼트립