오늘은 회사 서비스 구조를 모놀리식에서 모노레포로 전환하는 과정에 있어 고민했던 과정들을 기록해보고자 한다.
현재 다니고 있는 회사에서는 다양한 서비스들이 운영되고 있다.
메인 서비스 5개, 세부 서비스 2개, 어드민 서비스 2개, 디자인 시스템 1개까지 합하면 총 10개의 서비스가 존재한다.
각 서비스는 서로 다른 기술 스택을 사용하고 있으며, 새로운 서비스에 투입될 때마다 최신 버전의 라이브러리를 설치했었다. 예를 들어, 어떤 서비스는 Next.js + TypeScript, 어떤 서비스는 React + TypeScript, 또 다른 서비스는 React + JavaScript, Vue + JavaScript 등 다양한 스택을 사용하고 있었다.
게다가, 모든 프로젝트가 하나의 레포지토리에 포함된 모놀리식 레포지토리(monolithic repository) 구조로 되어 있었다.
모든 애플리케이션(=서비스)이 하나의 코드베이스에 통합되어, 백엔드, 프론트엔드, 데이터베이스 등 모든 구성 요소가 함께 배포되는 전통적인 방식이다. 주로 작은 규모의 애플리케이션에서 사용되며, 모든 코드를 단일 프로젝트로 관리하는걸 뜻한다.
초기에는 서비스 개수가 많지 않다 보니 큰 불편함이 없었다. 그러나 점점 서비스가 늘어나면서 여러 문제들이 나타나기 시작했다.
각 서비스마다 사용하는 라이브러리와 패키지의 버전이 다르게 설정되어 있다. 특정 서비스에서는 최신 버전을 사용하고, 다른 서비스에서는 구버전을 유지하는 식으로 일관성이 깨졌다.
여러 서비스에서 공통으로 사용하는 UI 컴포넌트나 로직이 있었지만, 각각의 서비스에 개별적으로 복사해서 사용해야 했다. 이는 유지보수를 어렵게 만들었고, 하나의 변경 사항을 여러 곳에서 반영해야 하는 비효율적인 구조를 초래했다.
새로운 서비스가 추가될 때마다 프로젝트를 처음부터 설정해야 했다. 공통적인 설정이 존재하지 않아 매번 동일한 초기 작업을 반복해야 했고, 이는 개발 생산성을 떨어뜨렸다.
모든 서비스가 하나의 레포지토리에서 관리되다 보니, 각 서비스마다 별도의 브랜치를 생성해야 했다. 심지어, 그 브랜치 내에서도 dev와 main을 나눠야 했기 때문에 협업 시 소스 코드 관리를 효율적으로 하기 어려웠다. 브랜치 전략이 복잡해지고, 작은 변경 사항도 여러 서비스에 영향을 미칠 가능성이 높아졌다.
처음부터 모노레포로 전환하고 싶었지만, 당시 진행 중이던 프로젝트도 있어서 구조 변경에 많은 시간을 투자하기 어려웠다.
게다가, 기존 모놀리식 구조에서 각 서비스의 기술 스택이 제각각이라 이를 한꺼번에 분리하고 종속성을 정리하는 데는 꽤 오랜 시간이 걸릴 것이라고 판단했다.
그래서 우선, 모놀리식 구조에서 발생했던 문제점 중 소스 코드 관리의 복잡성을 먼저 해결하고자 했다.
이를 위해 멀티레포(Multi-Repo) 구조로 전환하는 것이 최선이라고 생각했다.
멀티 레포지토리(Multi-Repo)란 각 서비스나 모듈을 별도의 레포지토리에서 개별적으로 관리하는 방식이다.
각 서비스는 독립적인 코드베이스를 가지며, 배포 및 종속성 관리도 각각 따로 이루어진다.
모놀리식에서 멀티레포로의 전환 자체는 오래 걸리지 않았다.
이미 기존 모놀리식 구조에서 각 서비스마다 종속성을 개별적으로 관리하고 있었기 때문에, 브랜치로 관리하던 서비스들을 각기 다른 레포지토리로 분리하는 방식으로 진행하면 됐다.
또한, 우리 회사에서는 CI/CD를 Jenkins(젠킨스) 를 통해 관리하고 있었는데,
각 서비스의 레포지토리에 별도의 Jenkins 파일을 두어 관리하는 방식으로 적용할 수 있었다.
멀티레포로 전환한 후, 가장 큰 변화는 서비스별 소스 코드 관리가 훨씬 명확해졌다는 점이다.
각 서비스가 개별적인 레포지토리를 가지게 되면서,
하지만 각 레포지토리가 독립적으로 관리되면서, 패키지 종속성 관리의 복잡성과 공통 UI 컴포넌트 및 로직의 공유 문제는 여전히 해결되지 않았다.
서비스마다 중복된 코드가 발생했고, 변경 사항을 반영할 때마다 각 레포지토리를 개별적으로 수정해야 하는 번거로움이 있었다.
결국, 유지보수에 드는 비용(=시간)이 점점 커지면서,
이러한 문제를 근본적으로 해결하기 위해 모노레포(Monorepo) 전환을 다시 고민하게 되었다.
모노레포로 전환하면 확실히 공통 코드 관리가 쉬워지겠지만, 구조를 통째로 바꾸는 작업이 만만치 않다는 점이 걸렸다.
서비스마다 독립적으로 운영되던 레포지토리를 하나로 합치는 과정에서 예상보다 많은 리소스가 필요할 것 같았고,
"모노레포가 아닌 다른 방법으로 위에서 언급한 불편함을 해결할 수는 없을까?" 라는 고민이 들었다.
특히, 기존 멀티레포 구조에서는
이 두 가지 문제가 가장 컸다.
그래서 다음과 같은 대안들을 고려해봤다.
스토리북으로 만들어놓은 공통 UI 컴포넌트나 공통 로직을 각 프로젝트에 서브모듈로 연결해서 사용하는 방식
✅ 장점
❌ 단점
공통 UI 컴포넌트나 공통 로직을 private 패키지로 배포하여, 사내 라이브러리처럼 활용하는 방식
✅ 장점
❌ 단점
💰 추가 비용 고려
이런 대안들을 고민해봤지만, 여전히 패키지 종속성 문제와 공통 코드 관리의 번거로움이 해결되지 않았다.
- 서브모듈 방식은 업데이트 반영이 번거롭고, 패키지 종속성 문제를 해결할 수 없었다.
- 사내 라이브러리 배포 방식은 중앙 관리가 가능하지만, 모든 프로젝트의 패키지 버전을 통일하는 문제까지 해결하지는 못하고 추가적인 비용도 발생된다.
결국, 장기적으로 유지보수 비용을 줄이고, 패키지 종속성과 공통 코드 관리 문제를 동시에 해결하기 위해 모노레포 전환이 필요하다고 판단했다.
다양한 해결책을 고민해봤지만, 장기적인 유지보수 효율성과 개발 생산성을 고려해
결국 모노레포(Monorepo)로 전환하기로 결정했다.
모노레포(Monorepo) 는 여러 개의 프로젝트(서비스)를 하나의 레포지토리에서 관리하는 방식을 말한다.
독립적인 애플리케이션이더라도 공통된 코드, 패키지, 설정 등을 쉽게 공유할 수 있어, 협업과 유지보수의 효율성이 높일 수 있다.
🔹 별도의 패키지 배포 없이 같은 레포지토리 내에서 즉시 공유 가능
🔹 모든 서비스에서 동일한 버전의 공통 코드를 사용하므로, 불필요한 버전 충돌 발생 방지
기존 방식
- 공통 UI/유틸을 각 프로젝트에 중복 추가로 인해 코드
- 코드 수정 시 모든 프로젝트를 개별적으로 수정해 유지보수 비용 증가
모노레포 전환 후
- 단일 코드베이스에서 직접 참조하여 코드 관리 가능
- 공통 코드 수정 시 한 곳에서만 변경하면 모든 서비스에 자동 반영
🔹 의존성(Dependencies)을 중앙에서 통합적으로 관리할 수 있음
🔹 패키지 업데이트 시 모든 서비스가 동일한 버전 유지 가능
🔹 중복 설치된 패키지를 줄여서 빌드 최적화
기존 방식
- 프로젝트마다 종속성이 달라서 버전 충돌 및 호환성 문제가 자주 발생
- 신규 패키지를 추가할 때 각 레포지토리에서 따로 설치해야 했음
모노레포 전환 후
- 하나의
package.json에서 공통 의존성을 통합 관리- 모든 서비스에서 동일한 패키지 버전 사용 가능
- 빌드 시 패키지를 공유하므로, 빌드 속도 최적화
🔹 서비스마다 일일이 환경을 설정할 필요 없이 동일한 도구와 설정을 적용 가능
🔹 신규 서비스 추가 시 복잡한 초기 환경 세팅 없이 빠르게 시작 가능
기존 방식
- 프로젝트마다 ESLint, Prettier, Webpack/Vite 설정이 제각각
- 신규 프로젝트를 만들 때 환경 설정을 처음부터 다시 해야 했음
모노레포 전환 후
- 공통 설정을 공유하여 일관된 개발 환경 유지
- 신규 프로젝트 추가 시 기존에 설정된 환경을 그대로 가져와 초기 세팅 없이 바로 개발 가능
이러한 장점들을 고려해 최종적으로 모노레포로 전환하기로 결정했고,
현재는 Turorepbo를 활용하여 모노레포 환경을 구축한 상태다.
Turborepo를 사용하면
등의 장점이 있어, 대규모 서비스에서도 효율적으로 활용할 수 있다.
오늘은 모노레포로 전환하기까지의 고민 과정을 정리한 포스팅이므로,
실제 모노레포로 전환하는 과정과 그 과정에서 겪었던 트러블슈팅은 이후 포스팅에서 자세히 다뤄보겠다. 😉