신규 기획 개발을 앞둔 2022년 12월, 프론트엔드 팀에선 다음의 문제를 직면했습니다:
모노레포 도구 선택 전 필요한 요구사항을 먼저 리스트업했습니다.
리스트업한 요구사항을 바탕으로 모노레포를 구현할 때 어떤 도구를 선택할 지 결정했습니다. 후보군은 Nx, Turborepo였습니다.
모노레포 구축 당시 대다수의 프로젝트에서 yarn classic을 사용 중이이었고, yarn berry로의 이관 비용을 제외하기 위해 yarn classic workspaces에 대해서만 고려했습니다.
yarn workspaces만 사용해도 코드 공유 목적은 충분했으나, 다른 모노레포 도구를 사용할 경우 다음과 같은 모노레포 기능을 사용할 수 있기에 후보군에서 제외하였습니다 (참고: Monorepo features).
1. 생태계가 크다.
생태계가 큰 nx는 turborepo와 달리 vscode extension( Nx Console)을 제공하고 있었으며, 플러그인 오픈소스가 많았습니다.
또한 이슈 발생 시 참고할 자료가 많았기에 빠른 개발환경 구축에 안정감을 더해주었습니다.
2. 문서에 사용법이 자세히 기술되어 있다.
Nx는 turborepo보다 문서화가 잘 되어있었습니다. 공식문서에서 다양한 솔루션을 제공할 뿐 아니라, Nx Core 팀에서 운영하는 블로그에서 새로 배포한 버전에 대한 안내, 환경 구축 팁, 문서화 팁 등을 포스팅했습니다.
3. package-based 방식 뿐 아니라 Integrated 방식도 지원한다.
package-based repository는 모노레포 내 각 패키지마다 package.json
을 관리하고, 서로를 package.json
을 통해 의존하는 방식입니다. 패키지 매니져 워크스페이스 등 일반적인 모노레포에서 의존성을 관리하는 방식입니다.
Nx는 package-based 방식 뿐 아니라 Integrated 방식도 지원합니다. Integrated repository는 패키지간 package.json
이 아닌 typescript imports를 통해 의존하고, root에 하나의 package.json
만을 관리하며 single version policy를 지킵니다.
Integrated 방식의 경우 기존 패키지를 모노레포에 추가할 때 빌드 도구 등을 모노레포에서 사용하는 방식으로 마이그레이션해야 하는 단점이 있지만, 모노레포의 규모가 커져도 의존성 관리가 용이한 장점이 있습니다.
당시 저희 팀은 각 프로젝트의 기술 스택이 크게 다르지 않았고, 프로젝트 개수가 많았기에 추후 의존성 관리가 용이한 integrated 방식이 적합하다고 판단했습니다. 또한 Integrated repository로 구성할 경우 Nx를 좀 더 프레임워크에 가깝게 사용할 수 있기에 저희가 신경 쓸 수 없는 부분을 Nx가 대신 해주길 바랐습니다.
4. 변경사항에 영향받는 범위를 affected
커맨드로 확인 및 제어할 수 있다.
모노레포는 많은 프로젝트를 하나의 디렉토리에서 관리하고 있기 때문에 규모가 커질수록 빌드 속도가 비례하여 증가합니다.
Nx는 이에 대한 솔루션으로 affected 커맨드를 제공합니다. 예를 들어, nx affected -t build
커맨드 실행 시 현재 코드 변경사항에 영향받는 워크스페이스 내 패키지를 리스트업하고, 해당 패키지 리스트에 대해서만 요청 작업(build
)을 실행합니다.
Nx에서 권장하는 파일 구조로 구성하였습니다.
/libs/
하위에는 아직 모노레포로 이관하지 않은 다른 앱에서도 공통으로 사용할 라이브러리 패키지(publishable)와 모노레포 하위에서만 사용하는 내부 패키지(buildable)를 관리하였습니다./apps/
하위에는 서비스로 운영 중인 앱 패키지를 관리하였습니다.rs-frontend/
└── libs/ # buildable한 라이브러리와 publishable한 라이브러리 존재
└── publishable-library-package-1
└── buildable-library-package-1
└── ...
└── apps/
└── app-package-1
└── ...
└── nx.json
└── package.json
└── tsconfig.base.json
모노레포에서의 DX를 향상하기 위한 기본적인 개발 환경을 구성하였습니다.
tsconfig.base.json
)을 두고 각 프로젝트 루트에서 이를 상속한 tsconfig를 별도로 관리하도록 하였습니다. 모노레포를 도입하며 Trunk-based development를 도입하였습니다. Trunk-based development는 trunk 브랜치(main
) 하나만을 두고 지속적으로 작은 단위의 코드를 trunk에 병합해나가는 버전 관리 전략입니다. 모노레포의 경우 코드 베이스 크기가 멀티 레포에 비해 크며, 병합 충돌이 일어날 경우 사이드이펙트가 커지기에 작은 단위의 코드를 보다 자주 병합할 수 있는 trunk-based development를 채택했습니다.
각 패키지 별로 독립적인 배포를 보장하기 위해 CI 액션은 공통으로, 배포 액션은 패키지별로 설정하였습니다. 사내에서 자동 배포하는 프로젝트는 없었기 때문에 라이브러리 패키지는 병합 시 바로 배포하는 전략(CI/CD)을 채택하고, 앱 패키지는 수동으로 배포를 트리거하도록 하였습니다.
각 앱 별로 배포 주기가 다르고 QA가 완료되어야만 상용배포 가능했기에 앱 패키지 배포의 경우 추가적인 장치가 필요했습니다. 이를 git tag를 이용한 태그 기반 배포로 해결했습니다.
코드 변경점의 경로에 따라 자동으로 라벨을 붙이고 라벨명을 참조하여 아래와 같은 컨벤션으로 trunk에 병합 시 태그를 자동으로 푸쉬하도록 하였습니다.
{package_name}/{version_number}
초기 구축은 첫 스텝이었습니다. 기존 프로젝트 이관 작업 및 모노레포 환경 개선 작업이 남아있었습니다. 한 달이라는 짧은 기간만이 주어진 상황이었기에 구축한 모노레포에서 기존 서비스 이관 전, 신규 서비스 런칭 작업을 우선적으로 진행하였습니다.
다음 글에선 초기 구축한 모노레포 환경에 어떤 문제가 있었고, 어떻게 개선해나갔는지에 대해 이어서 다뤄보도록 하겠습니다.