모노레포 이렇게 좋은데 왜 안써요?

MUSINSA tech
MUSINSA tech
Published in
13 min readSep 6, 2023

안녕하세요 무신사 커뮤니티 트라이브에서 프론트엔드 개발을 하고 있는 이도현입니다. 😄

저희 트라이브에서는 스냅, 패션톡, 체험단, 어필리에이트 등 고객의 신규방문과 재방문 경험을 강화하는 커뮤니티 제품들을 운영하고 있습니다.

여러 서비스를 더 효율적으로 운영하기 위해 저희 커뮤니티 트라이브 프론트엔드(FE)팀은 모노레포 전략을 선택했는데요. 이번 포스팅에서 모노레포 전략을 도입했던 여정과 도입 후 어떤 점들이 달라졌는지 소개하겠습니다.

모노레포 도입배경

커뮤니티 트라이브에서는 10개 이상의 서비스를 운영 중인데, 이 서비스들은 개별 담당자가 각각의 레포지토리로 관리하고 있었습니다. 작업을 하다보면 때때로 서로의 레포지토리에서 작업을 해야하는 상황이 발생했는데요. 이러한 상황에서 다음과 같은 문제상황에 직면했습니다.

멀티 레포지토리에서 겪은 문제상황

1) 프로젝트마다 서로 다른 컨벤션 (코드, Git/브랜치)

  • 프로젝트마다 서로 다른 린트 환경과 코드 패턴들 (eslint, prettier, stylelint, tsconfig)
  • 프로젝트마다 서로 다른 라이브러리/프레임워크 사용 (react, vue, next)

2) 새로운 서비스(프로젝트)를 구축하기 위한 비용

  • 새로운 프로젝트를 생성할 때마다 서비스의 환경과 린트 환경을 반복적으로 세팅
  • 무신사 내의 공통으로 사용하는 MDS(MUSINSA Design System 이하 MDS) UI 컴포넌트, 패키지들을 새로운 환경에 맞게 반복 구현

3) 중복된 코드와, 프로젝트 관리에 대한 비효율

  • 각 서비스에서 사용하는 프로젝트의 라이브러리 버전이 통일되지 않음
  • 공통 MDS 컴포넌트, 정책에 변경 사항이 생겼을 경우 모든 레포지토리 담당자에게 수정 요청
  • 히스토리가 레포지토리 별로 흩어져 있음

위와 같은 문제들로 인해서 해당 레포지토리 담당자 외의 다른 팀원이 해당 레포지토리에서 작업을 하려면 코드와 환경을 파악하기 위해 많은 시간이 발생했고, 새로운 사람이 프로젝트를 맡게 되었을 때 초기 온보딩 비용이 많이 들어갔습니다.

모노레포의 도입

이러한 문제점들을 개선하기 위해서 여러 명의 개발자들이 같은 환경에서 개발을 하고, 공통된 모듈을 사용할 수 있도록 모노레포를 도입하기로 했습니다.

모노레포란 ?
다수의 프로젝트를 한 개의 레포지토리 내에서 관리하는 소프트웨어 개발 전략 — Wikipedia

모노레포 도입에 앞서, 아래와 같은 5가지 초기 고민이 있었습니다.

1. 모노레포 통합 후 서비스별 배포 전략

2. 모노레포에 대한 학습 비용

3. 사용 라이브러리의 자유도

4. 기존 컨벤션과의 충돌

5. 공통 모듈을 생성해야하는 비용

사전에 학습과 조사를 통해 동료들과 이러한 고민들을 충분히 훑어보았고, 각각 아래와 같은 대안 및 해결책을 토대로 모노레포를 도입하게 되었습니다.

1. 모노레포 통합 후 서비스별 배포 전략 : 서비스의 배포는 독립적으로 진행

2. 모노레포에 대한 학습 비용 : 기존의 사용하던 yarn 패키지 매니저를 활용하고 학습 비용이 크지 않음

3. 사용 라이브러리의 자유도 : 일관된 개발자 경험을 위해 각 서비스마다 다양한 라이브러리를 사용하는 것을 지양

4. 기존 컨벤션과의 충돌 : 각 서비스에서 사용하던 컨벤션들을 토대로 컨벤션 패키지 생성

5. 공통 모듈을 생성해야 하는 비용 : 초기 공통 모듈에 들어가는 비용 대비 완성된 모듈로 인해 향상된 개발 생산성이 더 클 것으로 판단

하나가 되기 위한 과정 — 모노레포 도입 과정

1. 모노레포 도구 선택

모노레포 도입을 위해서는 먼저 도구를 결정해야 했습니다. 모노레포 구조를 도와주는 도구는 여러가지가 있는데요. 그 중 저희는 아래와 같은 이유로 yarn berry와 yarn workspace를 선택하게 되었습니다.

  1. PnP(Plug’n’Play)를 사용하여 라이브러리 간의 중복 의존성 문제, 비효율적인 패키지 설치 문제를 해결
  2. zero-Install 사용
  3. 기존 구성원들이 yarn을 사용하였기 때문에 러닝 커브가 낮음
  4. 타 도구들에 비해 활발한 커뮤니티 및 잘 작성된 공식 문서

2. 모노레포 구조

[그림1] 초기 모노레포 구조

처음에 모노레포를 도입하게 되면 위와 같이 하나의 레포지토리 내에 서로 다른 멀티레포 프로젝트가 존재하는 형태입니다. 이러한 구조에서 저희는 모노레포 구조를 잘 활용하기 위해 각 서비스가 가지고 있는 공통 코드를 분리하고, 코드 패턴을 통일하는 작업들을 진행했습니다.

3. 컨벤션

첫 번째로는 컨벤션입니다. 각 서비스에서 기존에 사용하던 컨벤션들을 취합하고, 공통된 부분과 앞으로 사용할 컨벤션을 모아둔 패키지를 생성했습니다.

[그림2] 공통 컨벤션 패키지 구조

이 패키지를 기반으로 각 서비스에서 상속 받아 사용하는 식으로 구성하였고, 적용 후 서로 다른 컨벤션들이 생기는 것을 방지하기 위해 서비스 내부 설정 파일에서 옵션을 덮어쓰지 못하도록 내부 규칙을 정했습니다.

4. 폴더 구조 & 아키텍처

다음으로는 폴더 구조와 아키텍처입니다. 프론트엔드에서 사용하는 폴더 구조와 코드의 패턴은 매우 다양한데요. 높은 자율성으로 인해 각 서비스의 폴더 구조와 코드 패턴은 담당자에 따라 매우 달라지게 됩니다.

저희는 여러 회의를 통해 서비스 영역의 폴더 구조도 하나로 통일하게 되었는데요. page-modules라는 폴더를 이용하여 각 페이지에 사용되는 모듈을 페이지별로 분리하도록 하였습니다.

5. page-modules 폴더 구조

먼저 도메인에 종속되지 않은 폴더는 root 레벨 폴더로 저장을 하고 각 페이지에서 사용되는 폴더는 page-modules 폴더에서 각 도메인 별로 관리될 수 있게 분리했습니다.

[그림3] page-modules 구조

다음과 같이 추천 페이지에서 사용되는 컴포넌트, 서비스 로직, hook과 같은 모듈들은 main-recommend라는 폴더 내부에 존재하고 팔로잉 페이지에서 사용되는 모듈들은 main-following이라는 폴더 내부에서 관리됩니다.

서비스 내부 페이지 간 공통으로 사용되는 모듈들은 다소 중복되더라도 각 페이지 별로 따로 생성을 했는데요.

그 이유는 공통 모듈들을 하나의 common 폴더에서 관리하게 될 경우 기획 당시 두 모듈의 모습과 사용처가 같다고 해도 도메인이 다르게 될 경우 서비스의 성장과 흐름에 따라서 다르게 변화할 가능성이 높다고 판단했습니다.

[그림4] 컴포넌트 예제 코드

하나의 공통 모듈을 사용하고 있는 두 페이지가 서로 다른 변경 사항이 생길 경우 위와 같이 분기 처리에 필요한 props나 if 문이 추가되면서 컴포넌트가 점점 비대해지고 파악하기 힘들게 됩니다.

그래서 저희는 이런 page-modules 폴더 구조를 사용하면서 각 페이지의 응집도를 높이고 페이지 간 의존성을 제거하는 방향으로 선택했습니다.

6. 패키지 구현

다음으로는 패키지 구현입니다. 현재 저희 모노레포는 아래와 같이 3가지의 대표 폴더가 존재합니다.

[그림5] 모노레포 내 폴더 구조
  • apps: 운영중인 서비스들을 관리하는 폴더
  • libs: 도메인에 종속되지 않고, 다양한 서비스에서 라이브러리 형태로 사용할 수 있는 폴더
  • packages: libs 패키지들을 사용해서 더 고도화된 기능을 사용하는 폴더

이런 대표적인 폴더들을 기반으로 피처 개발을 진행할 때 신규 패키지를 생성하거나, 기존에 있던 코드 마이그레이션 작업을 병행했습니다.

흩어져 있는 공통된 코드 조각들을 하나의 패키지로 생성하기 위해서, 트라이브 내의 구성원끼리 여러 차례 미팅을 통해, 기존의 로직과 정책을 파악하고 이해하는 시간을 가졌습니다. 이렇게 여러 요구사항을 반영하기 위해 초기에 많은 노력을 쏟은 덕택에, 트라이브 내에서 중복적으로 적용되었던 로직을 파악해서 제거하는 데 성공했고 또 트라이브 내 각 서비스에 얽혀있었던 로직의 결합도 느슨하게 하여, 의존성을 낮출 수 있었습니다.

이렇게 하나가 되기 위한 저희 팀의 작업들이 진행되었습니다. 초기 구축 단계에서는 피처 개발과 함께 동시에 진행되어야 하다 보니 시간도 많이 쓰게 되고, 패키지를 개발할 때 고민도 많이 되었지만, 결과적으로 매우 만족스러웠습니다. 그렇다면 모노레포 도입을 통해 어떤 긍정적인 변화가 생겼는지 소개해 보겠습니다.

모노레포가 된 이후

1. 일관된 개발자 경험

[그림6] 모노레포 도입 후 팀 내 회고 중 일부

첫 번째로는 모노레포 내에 존재하는 프로젝트들의 코드 컨벤션과 Git/Branch 전략이 통일되면서, 트라이브 내 개발자가 어떤 프로젝트를 하더라도 일관된 개발자 경험을 가지게 되었습니다. 기존에는 각 팀 담당자만 해당 레포지토리에서 작업을 진행했지만, 팀 내부 상황에 따라서 트라이브 내에 존재하는 서로 다른 팀끼리 팀 간 지원이 자유롭게 가능해졌습니다.

2. 향상된 개발 생산성

[그림7] 향상된 개발 생산성

다음으로는 패키지를 공유할 수 있게 되면서 생산성이 많이 향상되었습니다. 무신사 서비스 내의 공통으로 사용하는 MDS 컴포넌트 또는 공통 정책에 변경 사항이 생겼을 경우 기존에는 각자의 담당자가 각자의 레포지토리에 전부 수정을 해야 했지만 이제는 한 사람이 변경하면, 적용된 모든 서비스에 반영이 됩니다.

이로 인해 기존 대비 공통 사항에 대응하기 위한 팀 작업 공수 비용이 80% 이상 감소 되었고, 공통 패키지들로 인해 서비스 개발을 진행할 때 UI 작업에 들어가는 시간이 50% 이상 감소 되었습니다.

빠르게 성장하는 무신사 서비스에서는 새로운 프로젝트를 생성해야 하는 일이 잦은데요. 모노레포 내의 새로운 프로젝트를 세팅할 경우 코드 컨벤션, UI, API, 모니터링 도구들이 이미 준비되어 있어서, 프로젝트 세팅에 필요한 리소스를 최소화할 수 있습니다.

일례로, 최근 인플루언서들의 개인 SHOP을 제공하기 위한 큐레이터 숍 프로젝트를 진행했는데요. 모노레포 도입으로 인해, 프로젝트 생성부터 첫 런칭까지 1주일 밖에 걸리지 않았습니다. 😁

[그림8] 스냅 서비스 개발 환경 구조

또한 여러 프로젝트를 동시에 띄워야 하는 환경에서 한 개의 ide만 사용해서 프로젝트를 확인할 수 있기 때문에 편의성이 증가했습니다. 저희가 담당하는 제품 중 스냅서비스에는 vue(레거시)로 구현된 페이지와 next(신규)로 구현된 페이지가 존재하는데, 기존에는 각각의 프로젝트 환경에서 개발 서버를 실행시켜 줘야 했지만, 모노레포 구조로 인해 위의 이미지와 같이 여러 프로젝트를 동시에 띄울 수 있게 되었습니다.

( vue2로 이루어진 레거시 스냅 프로젝트를 신규 next 프로젝트로 마이그레이션 했던 스토리도 다음 포스팅에 작성 예정이니 많관부 😁)

3. 프로젝트 관리(+버전관리)

[그림9] 프로젝트 버전 관리

마지막 긍정적 변화로는, 프로젝트 버전관리가 굉장히 편해졌다는 점인데요. 모노레포 도입 전에는, 관리되지 않는 프로젝트의 경우, 라이브러리 버전이 레거시 상태로 남아있게 되었지만, 모노레포 도입 후에는 모든 패키지의 버전을 통일하여 함께 관리할 수 있기에, 프로젝트 관리가 훨씬 수월해졌습니다.

또한 공통 패키지들로 인해 중복 코드를 방지할 수 있게 되었고 패키지가 분리되어 있어서 패키지 간의 의존성과 종속성이 사라져 유지보수에도 유리하게 되었습니다.

[그림10] 무신사 피드 서비스

위와 같이 무신사 서비스에는 다양한 피드 서비스들이 존재하고, 이미지를 사용하는 페이지들이 많은데요, 피드 시스템, 이미지 최적화와 같은 모듈들을 공통으로 사용하여, 제품 전반적으로 퍼포먼스를 향상 시킬 수 있었습니다.

위에 사용된 고도화된 패키지들에 대해서도 다음 포스팅에서 소개드리도록 하겠습니다. 😁

마무리

이렇게 커뮤니티 트라이브의 모노레포 도입 여정기에 대해서 소개를 마치도록 하겠습니다. 마지막으로 무신사 테크 조직에는 격주 간격으로 기술을 공유하는 프라이텤이라는 세션을 가지고 있습니다.

프라이텤이란?

프라이텤(패션브랜드 프라이탁에서 착용한 것으로 Friday + Tech를 합쳐 탄생)은 무신사 테크부문의 구성원들이 자발적으로 배움과 경험을 공유하는 문화입니다.

저희 트라이브에서 진행한 모노레포 도입기는 무신사 테크의 다른 팀과도 공유하고 싶어, 프라이텤에서 “모노레포 도입 후 우리에게 생긴일 (feat. 커뮤니티 트라이브) “이라는 제목으로 발표를 진행했는데요. 발표를 통해 다른 테크부문 구성원분들이 질문을 주신 내용 중 몇 가지를 소개드리고 포스팅을 마치도록 하겠습니다. 🙇‍♂️

[그림11] 무신사 프라이텤 발표 후 질의응답 화면

프라이텤 질문 모음

Q : 공통 패키지 이름이 medusa인거 같던데 이유가 궁금합니다

A : 여러 팀이 하나로 모여졌다는 것을 비유하기 위해 medusa라고 지었습니다. 😁

Q : 소규모 팀이면 모노레포를 도입하기보다 멀티레포를 유지하는 게 나을 수도 있을 것 같은데, 도입해 보니, 어느 정도 규모부터 도입하는 게 좋을 것 같다고 느끼셨나요?

A : 모노레포는 팀 협업뿐만 아니라, 패키지를 분리하고 관리함으로 각 모듈간의 종속성을 관리할 수 있고, 유지보수에도 유리하다고 생각이 듭니다. 또한 소규모 팀일수록 모노레포의 관리 측면이 더 좋아지기 때문에 언제든 도입하면 좋다고 생각합니다!

Q: 처음에 다르게 일하는 방식인 팀이 합쳐졌을 때는 되게 카오스였을 것 같은데, 모노레포로 오기까지 어떤 미팅들, 어떤 시도들을 통해 여기까지 왔는지 그 과정도 좀 궁금합니다.

A: 주 1회 정기 FE 미팅을 진행했었고, 미팅을 진행할 때마다 현재의 이슈 상황, 앞으로의 계획들을 트라이브 내부에서 계속 논의를 진행했습니다. 초기에 모든 걸 통일하기 보다 작은 것부터 하나씩 맞춰나가다 보니 현재는 대부분의 작업 방식이 통일될 수 있었습니다.

Q : 공통 패키지가 수정되었는데, 특정 모듈에서는 아직 변경 사항이 반영되지 않아야 할 때는 어떻게 처리 하시는지 궁금합니다!

A : 현재는 모듈의 최신화를 유지하도록 하기 위해 항상 최신버전을 사용하도록 되어있는데요, 특정 서비스에서는 변경 사항이 반영되지 않아야 한다면, 브랜치 전략 또는 npm의 패키지를 올려서 버전 별로 관리하도록 하는 방법이 있을 것 같습니다.

Q : 여러 프로젝트들의 PR이 많이 올라와서 복잡해졌을 것 같은데 그런 문제는 없나요?

A : 저희는 집중 코드 리뷰시간을 하루에 3회 (출근, 점심 후, 퇴근)으로 정해서 그 때 집중적으로 코드리뷰를 진행하고, 그 외의 시간에도 PR을 확인할 수 있는 구성원이 먼저 확인하는 식으로 하고 있습니다.

Q : 공통모듈이 업데이트된다고 각 서비스들이 자동으로 최신화되어서 배포되는 것은 아닐까요?

A : 네 공통 모듈이 업데이트 됐을 경우 각 서비스에서는 추가로 배포를 해야 반영이 됩니다!

--

--