안녕하세요. 매쓰플랫 프로덕트 프론트엔트 개발자 유시온입니다.
작년 매쓰플랫 프로덕트 개발팀의 FE 개발 과제 중 가장 큰 이슈는 모노레포 전환이었습니다.
이와 관련해서 기술적으로 겪었던 과정과 결과, 그리고 개발 문화의 변화까지 소개해 드리려고 합니다.
# 도입 배경
기존 FE 파트의 프로젝트는 프로덕트 별로 한 개의 저장소를 통하여 관리하는 멀티레포 방식으로
운영되고 있었습니다. 멀티레포는 각 프로젝트가 독립적이며 서로에 대한 영향도가 낮다는
특징이 있습니다. 하지만, 이로 인해 공통 코드의 관리가 어려워질 수 있다는 단점이 존재합니다.
저희 FE팀 역시 프로덕트를 가로지르는 공통 코드의 유지 보수 방법이 큰 관심사 중 하나였습니다.
예를 들어 비즈니스적 관점에서는 학원이나 학생 도메인, 개발 관점에서는 tsconfig, eslint 등의 설정이 있었죠.
매쓰플랫은 각 프로덕트마다 디자인 시스템이나 도메인 등 코드가 공유될 수 있는 부분이 존재합니다.
이러한 문제를 해결하기 위한 시도는 프로젝트 공통 디자인 시스템 도입이 이루어지면서 시작되었습니다.
디자인 시스템으로 구성된 컴포넌트들은 말 그대로 공통 컴포넌트의 정수였기 때문에 멀티레포 구조와는 상성이 좋지 않았죠.
물론 모노레포로의 전환이 위 문제의 유일한 해결 방법은 아닙니다. 고려했던 다른 방법 중 하나는, 디자인 시스템을 npm이나 github package에 띄워서 사용하는 방법도 존재했습니다.
그러나 모노레포를 적용하면 얻을 수 있는 동일한 개발 환경과 CI/CD 관리 등의 DX 향상,
그리고 개발자 경험치 습득에 큰 이점이 있다고 판단하여 모노레포를 도입하기로 결정하였습니다.
마지막으로, 이런 종류의 개발 과제는 비즈니스적 임팩트가 적기 때문에 이해관계와 상황이 맞아떨어져야 하는데요. 디자인 팀의 우선순위 과제인 디자인 시스템 도입이 결정되며 함께 진행할 수 있게 되었습니다.
# 전환 준비
모노레포 관리 툴 선정
모노레포를 지원하는 다양한 라이브러리가 있지만 저희 팀에서 선택한 라이브러리는 Lerna + Nx입니다. Lerna는 2020년 이후 관리가 되지 않아 업데이트가 되지 않는 라이브러리였습니다. 그러나 Nx를 개발한 Nrwl 사에서 Lerna를 인수하여 관리함을 밝히고, Nx와 결합하여 Lerna를 사용할 수 있게 업데이트했습니다.
Nx와 Lerna의 조합으로 Lerna가 가졌던 모노레포 구축 속도의 단점을 해결하고 DX 적으로 많은 기능들을 제공했습니다. Nx와 Lerna를 함께 사용함으로 Npm Package에 올리고 관리하는 것에 이점이 있습니다.
위에서 설명했던 장점들과 기존까지의 생태계를 바탕으로 한 많은 선례들과 정보를 쉽게 찾을 수 있다는 점들을 고려하여 Lerna와 Nx를 선택하여 사용하게 되었습니다.
TO DO 리스트업
모노레포 전환은 ‘달리는 기차의 엔진을 바꾸는 일이다.’라는 말이 나올 정도로 큰 규모의 개발 과제였습니다.
그렇기 때문에 모노레포의 적용은 세부적인 논의와 계획을 세워 진행해야 했습니다.
모노레포를 전환하면서 커밋들과 PR을 정리해야 했고, 이전 프로젝트에 대한 관리, 기존에 사용하던 여러 Setting과 외부 연결된 Integration Tool의 확인도 필요했습니다. 또한, 만일을 대비해 전환 이후 빌드 배포가 잘 안되었을 때 바로 롤백이 될 수 있도록 롤백 플랜도 준비해야 했죠. 위의 고려 사항들을 내부적으로 논의하며, 저희 팀은 점진적인 전환 전략을 선택했습니다.
1차 작업으로 규모가 가장 큰 메인 프로젝트를 모노레포로 우선 전환했습니다. 이 과정에서 디자인 시스템 프로젝트를 모노레포에 포함시키고, 다른 프로젝트들은 1차 개발 이후 순차적으로 모노레포에 마이그레이션 하기로 결정했습니다.
메인 프로젝트의 레포지토리를 clone 하여 작업을 진행하고, 기존의 레포지토리는 archive 처리하기로 하였습니다. 이 방법으로 git history나 branch들이 사라지지 않고 clone된 레포지토리를 생성할 수 있습니다. 이때 로컬에 있는 잔여 코드를 push 해야 하고, PR은 옮겨지지 않기 때문에 merge 시키고 나서 작업이 진행되어야 합니다.
저희 팀 내에서 정리한 확인 목록입니다. 아래 목록들을 하나씩 짚으면서 전환 과정을 소개해 드리겠습니다.
Commit & PR 정리
옮겨야 할 Secret-Key / Integration Tool 확인
Repository Clone
CI / CD 적용 (Vercel - 전환 & 롤백 플랜)
브랜치 관리 전략
# 전환 과정
우선 github의 repository 내에서 사용하고 있는 다양한 secret-key나 hook, integration tool들을
파악해 두어야 합니다. 이는 클론을 하면서 옮겨지지 않는 값들이기 때문에 미리 확인하고 클론 후 재설정을
해두어야 합니다. 그리고 위에서 언급한 것처럼 commit과 PR도 옮겨지지 않기 때문에 미리 push 하고
merge를 시켜두어야 합니다.
현재의 레포지토리를 클론하여 새로운 레포지토리를 만들 경우 아래와 같은 script를 이용할 수 있습니다.
git clone --mirror {원본github주소}
mv {repository명}.git .git
git remote set-url origin {클론시킬github주소}
git push --mirror
Shell
복사
기존 레포지토리에서 새로운 레포지토리로 이전하면서 github과 연결된 slack bot이나 webhook 같은 외부 integration tool을 확인하고, action secrets 등을 미리 파악하며 clone 이후에 이들을 재설정 해주어야 합니다. 이 과정에서 사용되지 않는 setting들을 확인하고 정리할 수 있었습니다.
다음으로 빌드 배포 관련 작업을 진행했습니다. 저희 프로젝트는 모두 Vercel을 사용하고 있었고 모노레포
CI/CD를 지원했기 때문에 큰 문제 없이 적용할 수 있었습니다.
Vercel Project > Settings > Domains 탭에서 변경할 환경의 Edit 버튼을 누르면 Domain을 변경할 수 있는 form이 나오는데, 이를 원하는 도메인으로 변경하면 즉시 도메인이 변경됩니다. 이미 다른 프로젝트에서
사용 중이던 도메인을 입력하였더라도 도메인이 옮겨지고 즉시 반영됩니다.
vercel을 사용하여 전환 준비가 완료되었다면 롤백 플랜 역시 준비해야 합니다.
롤백이 용이하기 위해 clone 하여 새로운 레포지토리와 기존의 archive 레포지토리로 구분하였습니다.
전환을 적용한 것과 마찬가지로 롤백 과정 역시, vercel 프로젝트의 domain만 옮기면 됩니다.
# 브랜치 관리 전략
모노레포로 변경하면 브랜치를 관리하는 방법도 달라지게 됩니다. 여러 프로젝트가 한곳에서 섞이기 때문에 기존에 사용하던 방법대로 하기에는 무리가 있었습니다. 이에 따라 팀에서 어떤 브랜치 관리 방법을 고민했고 실행했는지 설명드리겠습니다.
모노레포 환경에서 많이 사용하는 브랜치 관리 전략은 trunk based development 입니다.
main 가지에서 배포가 이루어지고, main 가지에서 판 브랜치를 가능한 안전한 방법으로 개발하여
작은 단위로 병합하는 방법입니다. 해당 방법은 브랜치를 바로 main 브랜치에 push 시키므로 개발한 브랜치가 버그가 없음을 보장해야 하고, 브랜치 병합 간 코드 충돌을 줄이기 위해 작은 단위로 개발을 해야 합니다.
안정성을 보장하기 위하여 페어 프로그래밍이나 다양한 PR 규칙, 테스트 코드 등의 전제 조건이 필요합니다만,
이러한 방식은 저희 팀 개발 방식이나 여러 프로젝트가 모노레포로 사용되지 않고 주 프로젝트만 모노레포에 있는
모노레포 1차 반영 안에는 적합하지 않다고 판단하여 저희만의 방법을 다시 만들었습니다.
저희는 github flow와 유사하게 흐름을 가져가지만, 각 프로젝트 package 별로 다시 main branch와 sub branch들을 구분하여 관리합니다.
프로젝트 package가 늘어날 수록 병합 시 충돌 위험이 늘어나지만 아직 관리되는 프로젝트 package의 개수가 적은 만큼 sub tree도 적기 때문에 현재까지는 적용이 가능한 방법이라고 판단하였습니다.
각 sub tree들이 한 개의 package 단위를 뜻한다.
# 전환 중 이슈
이미 진행 중이던 프로젝트를 모노레포로 전환하는 중에 여러 이슈들이 존재했습니다.
babel-loader 적용
babel-loader는 webpack에서 javascript 파일을 변환시키는 라이브러리입니다. 이를 적용하기 위해서는 패키지 설치 후, webpack에서 loader를 명시하고 babel 파일을 작성하면 되지만, 모노레포에서 적용시키는 방법에는 작은 차이점이 존재합니다.
모노레포의 특성상 config 파일들의 공통화가 가능한데, babel 역시 공통 설정은 상위(root)에서 선언하고 이를 extends 하여
하위 레포지토리에서 사용하는 것이 가능합니다. 이때 확인해야 할 것은 하위 레포지토리의 webpack 설정 파일에서 rootMode
옵션을 켜 주는 것입니다.
webpack이 babel-loader의 선언을 확인하면서 babel 파일을 찾을 때, 기본적으로는 동일한 경로에서 우선적으로 찾게 됩니다. 이는 잘못된 설정으로 프로젝트 외부의 babel config 파일을 참조하지 않게 하기 위함입니다.
그러나 모노레포에서는 babel config 파일이 상위에 존재하는 경우가 있기 때문에, 이를 참조하기 위해서 상위 디렉토리를
참조하는 옵션으로 rootMode: 'upward' 를 선언해 주어야 합니다.
추가적으로 babelrcRoots 속성을 통해 root의 babel config만 적용할지, 각 프로젝트의 babel config도 적용할지 설정할 수 있습니다. 기본적으로는 전자가 설정되어 있기에, 모노레포 형식에서 상위와 하위 babel config 파일을 적용시키기 위해서는 babelrcRoots: ['.', '../packages/*'] 형식의 옵션을 주어야 합니다.
module: {
rules: [
{
test: /\.(js|ts)x?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
rootMode: 'upward',
babelrcRoots: ['.', '../packages/*'],
},
},
],
},
]
}
JavaScript
복사
Babel Config File Type
한 가지 더 babel 관련하여 확인해야 할 것은 babel config 파일의 형식입니다. babel config 파일 형식은 project-wide
형식과 file-relative 형식이 있습니다.
project-wide 형식은 프로젝트 전체에 대한 설정을 적용하는 방식으로 루트 디렉토리에 존재하는 하나의 파일만 babel-loader
에게 적용 됩니다. 여러 개의 babel.config 파일이 있을 경우 루트에 가장 가까운 파일만 적용됩니다. project-wide 형식을 설정하기 위해서는 babel.config.* 이름의 파일에서 설정해 주어야 합니다.
file-relative 형식은 디렉토리 하위에서 영향을 미치는 방식으로 패키지를 넘나들며 설정이 적용되지 않기 때문에 root에서 사용하기는 어렵습니다. .babelrc.* / .babelrc / package.json 이름의 파일에서 설정할 수 있는 형식입니다.
file-relative 형식의 파일로 project-wide처럼 사용하는 방식도 존재는 하지만, 이는 불필요한 파일이 반복되어서 생성되기
때문에 권장하지 않습니다. 공식 문서에서도 권장하고 있는 방식은 root에서 babel.config.json 파일을 통해 project-wide
설정을 잡고, 패키지 내에서의 세부 설정은 .babelrc.json 파일을 통해 적용하는 것입니다.
기타 이슈
babel config 파일 적용 순서와 관련하여 MD를 조금 사용했는데, babel config의 선언한 설정들은 설정에 따라서 적용되어야
하는 순서가 이미 정해져 있기도 합니다.
우선 babel의 설정은 plugin과 preset으로 나뉩니다. plugin이 preset보다 먼저 적용됩니다. plugin은 배열의 첫 아이템부터
적용이 되지만, preset은 배열의 마지막 아이템부터 적용이 됩니다. 이를 염두에 두고, 각 plugin, preset 라이브러리의 적용 순서를 확인할 필요가 있습니다.
해결이 오래 걸렸던 이슈 중 하나는 auto-import가 제대로 되지 않았던 문제입니다. 모노레포의 하위 패키지에서 참조하는
다른 패키지의 어떤 파일에 대해 auto-import가 되지 않았던 이슈가 있었는데, 이 부분이 제 개발 환경에서만 발생하였기에 해결에 시간이 오래 소요되었습니다.
제 케이스에서의 해결 방법은 IDE를 업데이트해 준 것이었습니다. stack overflow에서도 저와 동일한 케이스의 이슈가 올라온
적이 있었는데, IDE가 업데이트가 되지 않아서 타 패키지에 대한 auto-import를 지원하지 않는 경우도 존재했기에 이 부분도
확인해 줄 필요가 있습니다.
# 결과 및 향후 계획
모노레포 1차 전환 완료
모노레포 1차 전환 작업이 완료되었지만, 계획처럼 완벽하게 되진 않았습니다. Nx 라이브러리를 채택했지만, Nx에서 제공하는
여러 기능을 온전히 사용하지 못했습니다. 또한, Vercel CI/CD 시 각 monorepo package 내 외부 영향도가 없는 코드가 변경되었을 때는 해당 프로젝트만 빌드 되도록 하는 설정이 필요합니다.
위와 같이 해결해야 할 문제들이 있지만, 결과적으로 모노레포 전환은 성공적이었습니다. 모노레포가 적용된 스프린트 배포가
이슈 없이 원활히 진행되었고, 초기에 의도했던 디자인 시스템을 반영시킬 수 있었습니다. 앞으로 여러 프로젝트가 모노레포로
마이그레이션 될 예정이며, 코드 공유와 CI/CD 통합이라는 이점을 얻었습니다.
현재는 코드 공유 이점을 살리기 위하여 common과 design-system이라는 패키지를 만들어서 디자인 시스템 요소들과
여러 타입 및 유틸 함수들을 공통으로 관리하는 작업을 진행하고 있습니다. 이 과정에서 중복으로 관리되고 있던 코드들을 조금씩 리팩토링해 나가고 있습니다.
추가적으로 github package라는 기능을 도입할 예정입니다. 코드 공유의 이점을 누리기 위해선 프로젝트들을 모노레포에
마이그레이션 하는 것이 제일 좋지만, MD나 여러 이유로 마이그레이션이 어려운 프로젝트에서도 활용할 수 있도록 private package로 코드를 공유하여 다른 프로젝트에서도 사용할 수 있게끔 할 계획입니다.
오프라인 리뷰 및 공유 문화의 정착
모노레포로 전환하며 얻은 부수적인 효과 중 하나는 오프라인 리뷰와 공유 문화 정착입니다. 기존에 이런 문화가 없었던 것은
아니지만 리뷰와 공유에 대하여 자연스레 꺼내고, 독려하는 문화를 만드는 계기가 되었습니다.
모노레포는 전환 계획과 일정 등을 프론트엔드 팀 내에서 사전 공유하며, 피드백 받고 적용하는 과정을 거쳤습니다. 또한 전환
마무리 단계에서도 최종 변경 사항에 대해 공유하는 시간을 가졌습니다. 저희 팀은 이러한 리뷰와 공유 문화가 볼륨이 큰 개발에서 이점이 있다고 판단하게 되었습니다.
기존에는 볼륨이 큰 개발 건에 대해서 직접 참여한 개발자가 아니라면 문서와 PR comment만을 통해서 코드 리뷰를 보기 어렵다는 pain point를 느끼고 있었습니다. 그러나 작업 전/후 리뷰를 통해 작업의 전반적인 flow와 도메인 변경 사항을 미리 파악하기
때문에 코드 리뷰의 퀄리티가 상승하고 소요 시간이 줄어들게 되었습니다.
그 외에도 개발 진행 계획을 미리 공유하기 때문에 팀원들에게 더 나은 방법을 피드백 받고 반영하는 것이 PR 단계에서 피드백 받고 수정하는 것보다 쉽고 개발 시간이 단축된다는 장점도 있었습니다. 이러한 여러 장점을 모두가 공감하며 느꼈기 때문에 오프라인
리뷰는 팀 내 개발 문화로 정착되었습니다.
후기
모노레포 같은 큰 단위의 기술 도입 또는 개발 환경 변화에서는 롤백 플랜이 가장 중요한 부분인 것 같습니다. 이번 모노레포
전환에서 문제가 생겼을 때를 대비하여서 롤백 플랜을 만들고 검증하는 단계에서 힘을 실었습니다. 큰 단위의 작업에서는
QA에서 큰 이슈가 발생할 수 있습니다.
이를 바로 수정할 수 있다면 좋겠지만, 어떤 이슈가 발생할지 모르는 상황에서는 이슈를 수정할 때까지 문제 상황을 그대로
두는 것보다 롤백 후에 재작업이 더 좋을 수 있기 때문입니다.
모노레포 작업은 공수도 많이 필요했고, 개발 환경에 전반적인 변화를 일으켰습니다. 이 과정에서 더 나아진 부분도,
아직 부족한 부분도 있을 것입니다. 그러나 점진적으로 발전하려 하고 새로운 문화를 만든 것 자체만으로도 충분히
값진 경험이었던 것 같습니다.
나중에 또 진행하게 될 기술 도입에서는 더 DX에 중점을 두면서 팀적으로 편하고 보일러 플레이트 등의 작업을 줄일 수 있을
방향 위주로 작업을 진행해 보고 싶습니다.