모노리포 희망편, 절망의 리포가 희망의 리포로 부활하기까지 걸린 1년
토스는 100명 이상의 프론트엔드 엔지니어가 여러 제품을 개발하면서도 React 19, Next.js 15 등 동일한 개발환경을 유지하기 위해 모노리포와 의존성 카탈로그를 활용하고 있습니다. 단순히 모노리포를 사용하는 것만으로는 서비스별 의존성 버전 파편화와 느린 설치 속도 문제를 해결할 수 없었기 때문에, 핵심 라이브러리 버전을 표준화하는 카탈로그 전략을 도입했습니다. 그 결과 의존성 규모와 설치 시간이 크게 줄었고, 플랫폼 변경과 최신 기술 도입도 더 안전하고 빠르게 진행할 수 있게 되었습니다. ## 모노리포가 제공한 개발 일관성 - 토스의 모바일 제품 코드는 하나의 모노리포로 통합되어 있습니다. - 모든 서비스가 React, Next.js, TypeScript, 번들러, Linter 등 유사한 버전을 사용하도록 관리되었습니다. - 이를 통해: - React Concurrent Mode, React Server Components 같은 최신 기능을 여러 서비스에서 활용할 수 있습니다. - 서비스 간 공통 코드와 플랫폼 라이브러리를 쉽게 공유할 수 있습니다. - 플랫폼 변경사항을 전체 서비스에 일관되게 전파할 수 있습니다. - 제품이 많아도 동일한 개발환경을 유지해 사용자 경험과 개발자 경험을 함께 개선하는 것이 목표였습니다. ## 모노리포만으로 해결되지 않은 문제 - 서비스마다 React와 각종 라이브러리의 버전이 달라 의존성 트리가 복잡했습니다. - 오래된 서비스는 낡은 개발환경을 계속 사용하게 되어 개발 서버 속도와 API 사용성에서 큰 차이가 났습니다. - 의존성 종류와 버전이 많아 설치에 캐시가 있어도 1분 이상 걸리는 경우가 있었습니다. - 플랫폼 팀은 다양한 React 및 라이브러리 조합을 모두 테스트해야 했기 때문에 공통 라이브러리 변경이 어려웠습니다. - 서비스 개발자도 업데이트 후 문제가 발생할 가능성을 우려해 플랫폼 라이브러리 업데이트를 기피했습니다. - 결과적으로 오래된 의존성이 고착되고, 서비스와 플랫폼 양쪽의 유지보수 비용이 커졌습니다. ## 폴리리포의 한계 - 모노리포를 여러 개의 독립적인 리포지토리로 나누면 각 저장소의 의존성과 설치 부담은 줄어듭니다. - 그러나 다음 문제는 오히려 남거나 심해질 수 있습니다. - 서비스별 개발환경 파편화 - 공통 코드 공유와 업데이트 비용 증가 - 서비스마다 다른 개발 경험 - 플랫폼 변경사항의 일관된 전파 어려움 - 토스는 지속적으로 플랫폼을 유지보수하고 최신화해야 하므로, 폴리리포보다는 기존 모노리포의 의존성 문제를 해결하는 방향을 선택했습니다. ## 핵심 해결책: 의존성 버전 표준화 - 가장 근본적인 문제를 “서비스마다 핵심 의존성 버전이 모두 다르다”는 점으로 정의했습니다. - React, 컴포넌트 라이브러리(TDS), 상태 관리 라이브러리(Jotai), TypeScript, ESLint 등 약 10~20개의 주요 라이브러리를 표준화 대상으로 삼았습니다. - 핵심 의존성 버전을 통일하면: - 설치해야 할 의존성의 종류와 개수가 줄어듭니다. - 모든 서비스에서 비슷한 개발 경험을 제공합니다. - 플랫폼 라이브러리의 테스트 환경이 단순해집니다. - Breaking change에 대응하는 코드 변환 스크립트나 호환성 레이어를 만들기 쉬워집니다. - 서비스 개발자가 검증된 최신 라이브러리로 업데이트할 유인이 커집니다. - 대부분의 개발자는 “React가 필요하다”고 결정할 뿐 특정 버전을 직접 선택할 필요는 없다는 점도 표준화의 근거가 되었습니다. ## 카탈로그를 통한 버전 관리 - 토스는 서비스에서 권장하는 표준 라이브러리 버전 집합을 **카탈로그(Catalog)**라고 정의했습니다. - pnpm이나 Yarn의 카탈로그 기능을 사용해 모노리포의 공통 버전을 선언합니다. ```yaml catalog: react: ^18.2.0 jotai: ^2.18.1 ``` - 각 서비스는 `catalog:` 프로토콜로 버전을 참조합니다. ```json { "dependencies": { "react": "catalog:", "jotai": "catalog:" } } ``` - 안정 버전과 실험 버전을 별도 카탈로그로 관리할 수도 있습니다. ```yaml catalogs: stable: react: ^18.2.0 jotai: ^2.18.1 beta: react: ^19.1.0 jotai: ^2.20.1 ``` - 이후 서비스는 `catalog:stable`처럼 특정 카탈로그를 선택할 수 있습니다. - React, Next.js, TypeScript, TDS, 토스 앱 SDK 등 핵심 개발 라이브러리부터 카탈로그에 편입했습니다. ## 카탈로그 도입과 운영 방식 - 카탈로그 패키지는 릴리즈 전에 주요 사용 사례를 테스트 페이지로 검증했습니다. - 신규 서비스는 최신 카탈로그를 자동으로 참조하도록 스캐폴딩했습니다. - 개발자가 `yarn add`로 직접 패키지를 추가해도 카탈로그 버전을 사용하도록 했습니다. - 실수로 카탈로그를 사용하지 않는 경우를 CI에서 자동 검출했습니다. - 기존 서비스는 코드 오너와 함께 의존성을 카탈로그 참조 방식으로 일괄 마이그레이션했습니다. - 카탈로그 버전을 변경할 때는 기존 카탈로그를 직접 수정하지 않고 새 버전을 발행했습니다. - 일부 서비스에서 먼저 검증 - 안정성 확인 - 전체 서비스가 새 카탈로그로 수동 마이그레이션 - 업그레이드 비용을 낮추기 위해 코드 수정 스크립트와 AI Skill도 제공했습니다. ## 카탈로그 적용 후의 성과 - 서비스별 의존성 버전이 통일되면서 전체 의존성 규모가 크게 감소했습니다. - Yarn PnP의 `.pnp.cjs` 파일 크기: - 96MB → 15MB - 약 84% 감소 - 개발 서버 실행 시간: - 26.7초 → 20.3초 - 약 23% 개선 - 전체 의존성 설치 시간: - 528.4초 → 249.9초 - 약 52% 감소 ## 검증된 의존성과 구조적 개선 - 카탈로그에 포함된 패키지는 최소 한 개 이상의 서비스에서 동작을 검증해야 하므로 사용 신뢰도가 높아졌습니다. - 패키지 간 의존성도 엄격하게 관리할 수 있게 되었습니다. - 예를 들어 A 패키지가 B의 v1에 의존하는데 서비스가 B의 v2를 사용하는 식의 불일치를 예방할 수 있습니다. - 어떤 서비스가 어떤 버전을 사용하는지 파악하기 쉬워져 패키지 개발자가 대규모 구조 개선을 추진하기 수월해졌습니다. - 그 결과 RSC, TypeScript 7, Rspack, E2E 테스트 같은 급진적인 기술 개선도 비교적 빠르고 안정적으로 도입할 수 있었습니다. - 플랫폼 패키지의 개선사항이 서비스에 전달되는 경로가 표준화되어, 서비스가 최신 플랫폼의 혜택을 더 빠르게 받을 수 있는 기반도 마련되었습니다. ## 실용적인 결론 모노리포의 효과를 극대화하려면 저장소를 하나로 합치는 것만으로는 부족합니다. 핵심 의존성의 버전을 카탈로그로 표준화하고, CI 검증·자동 마이그레이션·단계적 릴리즈를 함께 운영해야 서비스 간 일관성, 설치 성능, 플랫폼 업데이트 속도를 동시에 개선할 수 있습니다.
원문 읽기(새 탭에서 열림)