모노레포를 실무에 적용한 뒤 한달. 후기
개요
작년 인프콘에서 모노레포를 처음 접했다.
https://www.youtube.com/watch?v=9RSNWt3AU-M
당시에는 그냥 재미있는 기술이 나왔구나 라는 생각을 했다.
이때 알게 된 모노레포에 대한 단편적인 생각은 아래와 같다.
- 대규모 시스템에 적합
- 모든 팀이 단일 리포지토리에서 작업하고 코드를 공유하므로 프로젝트의 규모가 커져도 일관성 유지가 됨
- 코드를 재사용하는데에 유리함
- 하나의 코드를 여러 프로젝트에서 가져다 사용할 수 있음
- 개발 환경을 일관되게 가져갈 수 있음
- 린팅, 테스트 스크립트 등을 하나로 통합
이때는 딱히 사이드 프로젝트나 회사에서 적용하기에는 검증도 안되어있고 딱히 필요한 부분도 없다는 생각이 들었다.
모노레포를 도입하게 된 배경
회사의 요구사항 :
우리가 운영하고 있는 웹서비스를 기반으로 하여 새로운 신규 시스템[들]을 빠른 시간 내에 런칭해야 한다.
기존 시스템의 문제점과 결론 :
그러나 레거시 시스템은 안정적으로 운영되고 있어도 아래와 같은 문제가 존재했다.
- 과도한 전역 상태 사용
- 의존성이 크고 복잡한 컴포넌트가 많음
- 컴포넌트 내에서 redux를 사용하거나 router를 사용하는 등
- 비즈니스 로직이 컴포넌트 내에 존재함
- 단순 Card 컴포넌트인데 컴포넌트 내부에 purchaseButton 같은 props가 존재한다던가
- 구버전의 React사용, 구버전의 next.js 사용
(요약하자면 스파게티 코드라는거다)
결론적으로, 레거시 시스템을 그대로 Clone 하여 새로운 웹서비스를 런칭하는 것은 추후 확장과 성능개선, 코드 관리에 큰 문제가 발생할 것 같다는 생각이 들었다.
즉 신규 시스템은, 레거시 시스템의 자원을 활용한 새로운 사상과 설계가 필요하다.
= 새로 만들어야 한다.
제약 조건 :
하지만, endDate는 넉넉하지 않았다.
- SaaS에 탑재할 서비스도 만들어야 하고(기존 레거시 시스템 기반)
- 내부적인 비즈니스 로직만 동일한 완전히 새로운 웹서비스도 만들어야 한다.
- 또한 일본 시장에 진출할 신규 서비스도 만들어야 한다.
→ 그리고 이 3개의 서비스를 모두 완성하는데에 필요한 시간은 2달남짓.
어떻게하면 새로 프로젝트를 만들면서, 생산성을 끌어올리고, 퀄리티도 유지할 수 있을까?
의사결정 :
생산성을 끌어올리고 퀄리티를 유지하기 위한 고전적이고 가장 확실한 방법은
“코드의 재사용을 줄이는 것”이다.
하나의 프로젝트 내에서도 이를 극대화 하기위해 특정 기능, 혹은 UI를 재사용하기위해 컴포넌트를 사용하고, 이를 체계적으로 분리하기 위해 Atomic pattern 같은 것을 도입한다.
유사한 3개의 프로젝트에서 동일한 컴포넌트를 사용할 수 있다면,
하나의 작업으로 3개의 프로젝트의 진도를 뺄 수 있다면
당연히 생산성은 올라갈 것이다.
그렇다면 하나의 컴포넌트를 여러개의 프로젝트에서 사용하기 위해서 필요한 것은 무엇일까?
아무래도 컴포넌트를 라이브러리 형태로 export 하는 것이 일반적인 방법이다.
그러나 더 좋은 방법도 있다.
모든 프로젝트를 하나의 레포에 때려넣고
프로젝트에서 공통으로 사용할 수 있는 컴포넌트 프로젝트를 추가로 만드는 것이다.
그리고 해당 컴포넌트 프로젝트를 각각의 프로젝트에서 가져다가 쓸 수 있다면,
하나의 작업으로 여러개의 프로젝트의 진도를 뺄 수 있는 것이다.
그리고 이것을 가능케 하는 것이 모노레포라고 할 수 있다. (후술하겠지만 그 외의 모노레포의 매우 좋은 장점들이 부가적으로 따라왔다)
팀원들을 설득하고, 방향성을 논의하는 것은 의외로 어렵지 않았다.
모두가 레거시 시스템에 불만을 가지고 있었고,
기술적, 경험적으로 도움이 될 거라는 생각에
모노레포가 가지고 있는 잠재적인 위험성과 한계에 대해서는 모두가 쉬쉬했던 것 같다.
(이건 중니어들의 고질적인 문제라고 생각함. 어 저게 잘나간다고? → 써보자!)
팀원들을 설득하기 위해 조사했던 많은 자료들이 무색하게 모노레포 도입은 프리패스로 승인되었다.
도입 과정
도구 및 워크플로우 설정 :
모노레포를 도입하기 위한 첫 단계는 도구와 워크플로우를 선택하는 것이었다. 우리는 여러 옵션을 고려한 후, 다음과 같은 도구들을 선택했다:
-
turborepo :
여러 선택지가 있었지만, 레거시 코드를 최대한 활용하기 위해 next.js 기반의 프로젝트를 사용할 것이고, 대부분의 기능이 다른 선택지들과 큰 차이가 나지 않았다. 또한 가장 인기 많은 도구이다.
강력한 빌드 캐싱기능도 유효했다. 3개의 대규모 프로젝트를 동시에 개발하는데에 빌드 시간은 매우 중요한 문제다.
-
npm
pnpm을 써볼까 했는데 팀원들이 이미 npm에 익숙해져 있고, 이를 사용하는 데 별다른 문제가 없었기 때문에 새로운 패키지 매니저로 전환하는 데 따르는 학습 곡선을 피하기 위해 npm을 계속 사용하기로 했다.
-
typescript
-
eslint와 prettier
-
Storybook
프로젝트 구조 설계 :
기본적으로 터보레포의 기본값을 따라간다.
-
docs 디렉토리
스토리북을 여기에 모아놓았다. 컴포넌트에 대한 가이드 및 디자인 시스템을 mdx로 만들어서 넣어놓았다.
-
app 디렉토리
독립적으로 실행 가능한 애플리케이션(웹서비스)를 모아놓았다.
-
packages 디렉토리
공통 컴포넌트, 유틸리티, ts 및 lint config 등을 모아놓았다.
이 디렉토리에 있는 패키지들은 각 애플리케이션에서 공유할 수 있다.
단계별 도입 과정 :
- 초기 설정:
- Turborepo와 npm을 사용하여 기본적인 모노레포 구조를 설정하고, TypeScript 환경을 구성했다.
- 각 패키지와 애플리케이션에 대한 기본 설정 파일들을 작성하고, 초기 빌드와 테스트가 성공적으로 수행되는지 확인했다.
- 공통 코드 분리:
- 기존 레거시 시스템에서 공통으로 사용될 수 있는 컴포넌트와 유틸리티 함수를
packages디렉토리로 분리했다. - 이를 통해 코드 재사용성을 높이고, 각 애플리케이션에서 공통 코드를 쉽게 사용할 수 있도록 했다.
- 기존 레거시 시스템에서 공통으로 사용될 수 있는 컴포넌트와 유틸리티 함수를
- ESLint와 Prettier 설정:
- 모든 패키지와 애플리케이션에 일관된 코드 스타일과 린팅 규칙을 적용하기 위해 ESLint와 Prettier를 설정했다.
- 팀원들이 동일한 코드 스타일을 유지할 수 있도록 각 프로젝트에 공통 설정 파일을 추가했다.
- Storybook 통합:
packages디렉토리에 있는 공통 컴포넌트들을 Storybook을 통해 독립적으로 개발하고 테스트할 수 있도록 설정했다.- 이를 통해 컴포넌트 개발 생산성을 높이고, UI 일관성을 유지할 수 있었다
도입 결과
도입 후 한달이 지났다.
다음주부터 SaaS 웹서비스는 통합테스트에 진입하며, 일본웹은 초기단계, 신규 웹서비스는 열심히 진행중이다.
처음에 SaaS 웹서비스를 먼저 시작하고 어느정도 작업이 진행되고나서부터 신규 웹서비스를 병렬적으로 진행했는데, 컴포넌트를 재활용하면서 빠른 속도로 작업을 진행할 수 있었다.
사용하면서 느낀점은 아래와 같다.
-
공통 컴포넌트를 굉장히 공들여 만들게 된다:
- 여러 프로젝트에서 다양한 목적으로 활용할 수 있도록 최대한 비즈니스 로직과 분리해야 하고, 여러 상황에 대응할 수 있는 컴포넌트로 만들어야 한다. 이로 인해 기본적인 컴포넌트들의 사용성이 개선되었다.
-
컴포넌트와 Router, Redux 등의 완벽한 분리:
- 결국 컴포넌트는 일급 함수로 만들지 않으면 여러 프로젝트에서 사용하기 어렵다. 따라서 일관성 있는 컴포넌트를 의식적으로 만들 수밖에 없다. 컴포넌트에서 router, redux 를 전혀 사용하지 않는다.
→ 결과적으로 프로젝트 내에서 redux가 사라졌다.(아직까지는 딱히 사용할 일이 없었다)
-
빈번한 Merge:
- 여러 프로젝트에서 공통 코드를 사용하다 보니 Merge가 빈번하게 발생한다.
-
작업 하나하나에 신경을 쓰게 된다:
- 모든 작업이 여러 프로젝트에 영향을 미칠 수 있으므로, 각 작업에 더 신경을 쓰게 된다.
모노레포 도입으로 인해 여러 프로젝트 간의 코드 재사용성과 일관성이 크게 향상되었음을 느낄 수 있었다. 이로 인해 생산성과 코드 품질 모두에서 긍정적인 변화를 경험하고 있다.
그러나 이 경험은 어디까지나 새로운 시스템에 모노레포를 도입했을 때의 이야기이다. 다음과 같은 경우에는 모노레포 도입이 적합하지 않을 수 있다:
- 기존 대규모 프로젝트를 모노레포로 마이그레이션:
- 이미 운영 중인 대규모 프로젝트를 모노레포로 전환하는 과정에서 많은 어려움이 발생할 수 있다. 기존의 빌드 및 배포 파이프라인을 재설정하고, 의존성 문제를 해결하는 데 상당한 시간이 필요할 수 있다.
- 각각의 프로젝트의 독립성이 큰 경우:
- 프로젝트 간의 상호 의존성이 적고 독립적으로 운영되는 경우에는 모노레포의 장점이 크게 부각되지 않을 수 있다. 오히려 불필요한 복잡성을 추가할 수 있다.
- 팀의 규모와 구성:
- 작은 팀이나 프로젝트: 작은 팀에서는 모노레포가 오히려 더 적합할 수 있다. 개발자 수가 적고, 공유하는 코드의 양이 상대적으로 적기 때문에, 빈번한 merge와 변경에도 부담이 덜하다. 또한, 모노레포를 통해 공통된 코드를 재사용함으로써 개발 속도를 높이고, 협업 효율성을 극대화할 수 있다.
- 대규모 팀: 대규모 팀에서는 여러 팀이 동시에 같은 리포지토리에서 작업하기 때문에, 코드 충돌과 병합 충돌이 빈번하게 발생할 수 있다. 이는 개발 속도를 저하시킬 뿐만 아니라, 코드 품질에도 영향을 미칠 수 있다. 또한, 의존성 관리, 빌드 시간 최적화, 권한 관리 등의 문제가 복잡해질 수 있다.
실제로, 우리는 개발자 3명의 소규모 조직이었고, 공유하는 코드의 양이 서로 감당 가능한 수준이었기 때문에 빈번한 merge와 변경에도 큰 문제가 없었다.
따라서, 모노레포 도입은 프로젝트의 특성과 팀의 상황을 면밀히 검토한 후 결정해야 한다.
새로운 시스템 구축이나 프로젝트 간의 코드 공유가 빈번한 경우에는 큰 장점을 제공할 수 있지만, 모든 상황에 적합한 것은 아니다.
모노레포 이후 조직이 성장하면서 겪은 변화는 [[KnowledgeBase/Blog/잡다한 글/모노레포 절망편, 14개 레포로 부활하기까지 걸린 1년]]을, CI/CD 파이프라인 구성은 [[KnowledgeBase/Blog/잡다한 글/GitHub Actions로 CI CD 파이프라인 구축하기]]를 참고할 수 있다.
댓글
첫 번째 댓글을 남겨보세요.