요즘IT
위시켓
콘텐츠프로덕트 밸리
요즘 작가들컬렉션물어봐
콘텐츠
프로덕트 밸리
요즘 작가들
컬렉션
물어봐
새로 나온
인기
개발
AI
IT서비스
기획
디자인
비즈니스
프로덕트
커리어
트렌드
스타트업
서비스 전체보기
위시켓요즘IT
고객 문의
02-6925-4867
10:00-18:00주말·공휴일 제외
yozm_help@wishket.com
요즘IT
요즘IT 소개작가 지원
기타 문의
콘텐츠 제안하기광고 상품 보기
요즘IT 슬랙봇크롬 확장 프로그램
이용약관
개인정보 처리방침
청소년보호정책
㈜위시켓
대표이사 : 박우범
서울특별시 강남구 테헤란로 211 3층 ㈜위시켓
사업자등록번호 : 209-81-57303
통신판매업신고 : 제2018-서울강남-02337 호
직업정보제공사업 신고번호 : J1200020180019
제호 : 요즘IT
발행인 : 박우범
편집인 : 노희선
청소년보호책임자 : 박우범
인터넷신문등록번호 : 서울,아54129
등록일 : 2022년 01월 23일
발행일 : 2021년 01월 10일
© 2013 Wishket Corp.
로그인
요즘IT 소개
콘텐츠 제안하기
광고 상품 보기
개발

모노레포 절망편, 14개 레포로 부활하기까지 걸린 1년

FEConf
15분
1시간 전
264
에디터가 직접 고른 실무 인사이트 매주 목요일에 만나요.
newsletter_profile0명 뉴스레터 구독 중

FEConf2025에서 발표한 <모노레포 절망편, 14개 레포로 부활하기까지 걸린 1년>을 정리한 글입니다. 플렉스 웹 제품의 모노레포가 직면했던 문제점과 이를 해결하기 위해 14개의 레포지토리로 분리하고 정기 배포를 중단하기까지의 과정, 그리고 이 과정에서 얻은 세 가지 핵심 교훈을 공유합니다. 본문에 삽입된 이미지의 출처는 모두 이 콘텐츠와 같은 제목의 발표 자료로, 따로 출처를 표기하지 않았습니다.

 

모노레포 절망편, 14개 레포로 부활하기까지 걸린 1년

플렉스 김종혁 개발자

 

플렉스 웹 제품은 일주일에 한 번 모든 제품이 함께 배포되는 정기 배포 체제로 운영되고 있었습니다. 웹 클라이언트 플랫폼 팀은 이 프로세스를 끝내고 각 스쿼드가 독립적으로 배포할 수 있도록 시스템을 바꾸는 작업을 진행했습니다. 그 과정에서 거대한 모노레포를 14개의 레포지토리로 분리하는 대규모 작업을 1년 5개월에 걸쳐 수행했습니다.

 

플렉스팀은 3년 동안 프론트엔드 개발자가 3배로 늘었고, 제품의 크기도 폭발적으로 커졌습니다. 팀과 제품이 커지면서 코드베이스는 거대한 도전에 직면했고, 이를 해결하는 과정에서 얻은 세 가지 핵심 교훈을 공유하고자 합니다. 프로젝트는 상당히 지난했고 팀원들도 많이 지쳤습니다. 하지만 그 틈바구니 속에서 생생한 교훈과 배움이 있었고, 비슷한 상황에 있는 분들께 도움이 되기를 바랍니다.

 

 

모노레포의 시작과 문제점

플렉스 2.0과 모노레포 채택

플렉스 웹 프론트엔드는 2021년 플렉스 2.0 프로젝트를 시작하면서 모노레포 구조를 채택했습니다. 플렉스는 HR B2B SaaS로서 다양한 기능들이 통합되어 가치를 만들어내는 올인원 플랫폼을 지향했습니다. 각 기능들은 도메인이 깊고 서로에게 강하게 의존하고 있었기 때문에, 다른 도메인의 데이터와 UI가 복잡하게 엮여야 통합의 가치를 전달할 수 있었습니다.

 

모노레포 안에는 각 비즈니스 로직이 들어간 도메인 패키지들이 자리 잡았습니다. 이 패키지들은 모노레포 내에서 다른 제약 없이 자유롭게 가져다 쓸 수 있었고, 패키지에 대한 버저닝도 따로 하지 않았습니다. 패키지와 애플리케이션 코드를 동시에 변경할 수 있는 구조로 시작했습니다.

 

기술적으로는 Yarn Workspaces를 사용해서 워크스페이스 의존성으로 내부 패키지들을 연결했고, Turborepo를 사용해서 의존성을 추적 및 관리했습니다. Turborepo는 각 패키지의 package.json에 명시된 의존 관계를 기반으로 패키지 단위의 태스크 실행과 의존성 추적을 돕는 도구였습니다. 하위 패키지가 바뀌어서 빌드를 새로 해야 하는 경우, 의존하고 있는 상위 패키지들도 연쇄적으로 빌드되어 애플리케이션 빌드 및 배포까지 이어지는 구조였습니다.

 

 

복잡성의 증가

이러한 구조는 플렉스 2.0을 시작하는 초기에는 제품을 빠르게 확장하고 만드는 데 도움이 되었습니다. 그러나 모노레포 위에서 함께 개발하는 개발자의 수와 제품의 크기가 커지면서 문제가 시작되었습니다. 공유되는 도메인 패키지들이 점점 많아지고 의존성이 점점 더 복잡해져서 변경 추적이 제대로 되지 않는 상황이 발생했습니다.

 

결국 바닥에 있는 패키지 하나만 수정해도 의존성이 복잡하고 꼬여 있어서 무조건 전체 앱을 다 배포해야만 하는 상황이 되었습니다. 모노레포라고 부르기도 힘들고 모놀리식에 가까운 상황이 된 것입니다.

 

의존 관계를 표현하기 위해 그래프를 그려주는 툴들을 사용해 봤지만, 그래프 툴이 Out of Memory 에러를 내며 중단되기도 했습니다. 결과적으로 나온 그래프는 우주와 삼라만상이 들어가 있는 것처럼 보이는 엄청난 의존성 덩어리였습니다. 이 거대한 덩어리는 무서워서 정리는커녕 제대로 들여다보기도 힘든 상태였습니다.

 

 

정기 배포의 시작

이런 사연으로 개발자가 작업한 코드가 정확히 어떤 앱에 영향을 미치는지 알 방법이 없어졌습니다. 배포나 롤백이 누락되거나 장애가 빈번하게 발생하게 되었고, 결국 2022년에 정기 배포를 시작하게 되었습니다.

 

모든 변경이 일주일에 한 번만 프로덕션에 나갈 수 있게 제한하고, 해당 정기 배포 진행 시에 챕터 전체가 그 시간에 모여서 변경이 잘 적용되었는지 모니터링하는 방식이었습니다. 프론트엔드 개발자들 사이에서 정기 배포 당번을 뽑았고, 정기 배포 당번이 되면 수행해야 하는 긴 체크리스트도 만들어졌습니다.

 

정기 배포를 하게 된 것은 좋은 일이 아니었습니다. 정기 배포를 하는 계기 자체가 안정성이 부족하다는 의미였고, 빠르게 고객에게 전달되어야 할 변경들이 계속 밀리는 경우가 생겼습니다. 모노레포 위에서 프론트엔드 개발자들은 서로의 발을 밟으면서 개발을 하고 있었습니다. 공유 레이어에 있지만, 관리되지 않고 버려져서 누구도 건들기 힘든 코드들이 계속 생기고 있었고, 제품이 커지는 속도는 한 번도 느려지지 않았습니다. 모노레포는 불타고 있었습니다.

 

 

정기 배포 그만두기 프로젝트

2024년 2월, 정기 배포를 시작한 지 2년이 채 되지 않았을 때 정기 배포 그만두기 프로젝트가 시작되었습니다. 여기서 중요한 포인트는 정기 배포를 그만두는 것이 목표였고, 모노레포를 해체하는 것 자체가 목표는 아니었다는 점입니다. 정기 배포를 그만두기 위해서 모노레포를 해체해야겠다고 결정한 것입니다.

 

모노레포 해체 과정

모노레포를 어떻게 뜯었는지 살펴보면, 먼저 바닥 의존성에 가까운 공용 코드들을 모두 뜯어서 레이어링하고 역방향 의존을 제거했습니다. 이것을 Packages, Design System, Services 3개의 레이어로 정의해서 별개의 레포로 분리하는 것부터 시작했습니다. 분리된 패키지 코드들은 빌드하고 버저닝을 해서 레지스트리에 올려서 사용하게 했습니다. 이를 통해 하위 패키지 변경을 버저닝으로 막을 수 있게 되면서, 하위 패키지 변경이 상위 애플리케이션으로 전파되는 것을 통제하고 거부할 수 있게 되었습니다.

 

각 애플리케이션을 개별 레포지토리로 분리하는 작업은 모노레포에서 하나씩 일정을 정해서 진행했습니다. 처음에는 하나의 앱을 개별 레포로 뜯어내는 데 두 달이 걸렸지만, 의존성이 정리되고 마이그레이션 노하우가 쌓이면서 점점 더 빠른 속도로 애플리케이션을 분리할 수 있었습니다. 전체 애플리케이션을 다 뜯는 데는 약 8개월이 걸렸습니다.

 

개별 레포로 분리하니 특정 코드 변경 시 영향을 받는 애플리케이션을 쉽게 특정할 수 있게 되었고, 각 레포에서 어떤 앱을 언제 배포해야 하는지 자유롭게 정할 수 있게 되었습니다.

 

 

정기 배포 그만두기의 성과

이 모든 작업을 9개의 마일스톤으로 나누고, 각 마일스톤은 짧으면 한 달, 길면 4개월까지 진행했습니다. 작업 진행 경과를 높은 해상도로 가시화하려고 노력했고, 무슨 일이 언제 끝나는지 진척도를 명확하게 표현했습니다. 고된 작업이었지만 이런 가시화 덕분에 끝까지 해낼 수 있었습니다.

 

2024년 7월 초, 14개의 레포로 분리를 완료하고 전사 공지로 3년간 162번 진행된 프론트엔드 정기 배포를 그만둔다는 소식을 전했습니다. 2022년 최초의 정기 배포 당번이었던 발표자가 2024년 마지막 정기 배포를 끝내게 되었다는 점은 의미가 깊었습니다. 뿌리 깊은 기술 부채를 수정하는 것이 요원해 보였지만, 팀이 포기하지 않고 결국 해결해 낸 순간이었습니다.

 

 

핵심 교훈 3가지

팀과 제품에 맞는 적정 코드베이스 구조를 만들기 위해 꼭 알아야 하는 세 가지를 이제 말씀드릴 수 있습니다.

 

1. 경계 없이 코드를 공유하지 말 것

경계가 없으면 코드는 무섭게 공유되고 결합됩니다. 모노레포냐 폴리레포냐가 중요한 것이 아니라 경계가 있느냐 없느냐가 훨씬 중요합니다.

 

Product Stock Checker 사례

모노레포에서 의존성이 복잡해지고 제품 안정성이 위협받는 사례를 하나 살펴보겠습니다. Product Stock Checker라는 패키지가 있다고 가정해봅시다. 이것은 개발자 A가 만들었고, 2개의 앱에서 상품 재고가 몇 개 남았는지 조회하는 UI 컴포넌트를 공유하기 위해 만들어졌습니다. 그런데 개발자 B가 다른 앱에서 이 상품 재고 조회 컴포넌트를 가져다 쓰고 싶었지만, 본인이 받은 디자인과 기획 의도에 맞지 않아서 속성을 하나 추가했습니다. React 컴포넌트로 치면 prop 하나를 뚫은 것입니다. 

 

기존 컴포넌트에 인자를 하나 넣어서 부족한 상품 재고를 재주문할 수 있는 버튼이 나타나도록 수정했습니다. 속성을 바꾸면 갑자기 버튼이 뜨게 된 것입니다. 재주문과 관련된 하위 패키지를 추가하고, 이 패키지에 앱 의존 관계도 하나 더 추가하게 됩니다.

 

이렇게 되면 패키지의 원래 취지가 모호해지고, 앱 3개의 의존이 생겼는데 기존에 의존하고 있던 두 곳에서는 이 변경사항을 알지 못합니다.

 

 

얼마 후 개발자 C가 재고가 떨어지면 알람을 주는 패키지를 만들고 있었습니다. 재고를 조회하는 방법을 넓은 코드베이스에서 찾다가 Product Stock Checker 패키지를 발견했는데, UI 컴포넌트가 아니라 그 안에 있는 getStock이라는 함수를 패키지에서 export해서 자신의 모듈에서 사용합니다. 기존 앱과 새로운 앱의 의존 관계가 또 추가됩니다.

 

그러다가 원래 이 패키지를 만들었던 개발자 A가 퇴사하면, 이때부터 이 패키지는 건들면 무조건 터지는 위험한 코드 취급을 받게 됩니다. 나중에 개발자 D가 용감하게 Product Stock Checker 패키지의 getStock 함수를 고쳤는데 실수를 해서, 그 함수에 의존하는 모든 앱에서 장애가 발생합니다.

 

 

예시가 극단적으로 보일 수 있지만, 복잡하고 큰 코드베이스에서는 실제로 일어날 수 있는 일입니다. 2022년 정기 배포를 시작할 때 이런 크고 작은 일들이 계속 일어났습니다.

 

효자손과 빵

개발자들은 가장 빠른 작업 경로를 위해서라면 자신의 작업 환경에서 가용한 코드들을 모두 사용하고, 생산성을 위해서 그것이 권장되어야 한다고 생각합니다. 이런 방식으로 코드를 사용한 개발자를 비난하면 조직의 효율성을 생각하지 않는 사람이 됩니다. 규칙이 없이 모든 코드를 공유할 수 있는 모노레포는 그 복잡성을 감당하기가 너무 힘들어집니다.

 

키가 아직 다 크지 않은 어린아이가 선반 위에 맛있는 빵이 있는데 자기 키보다 높은 선반에 있다고 해봅시다. 이 아이가 저 빵을 꺼내는 것을 포기할까요? 포기하지 않습니다. 효자손을 가지고 와서 어떻게든 선반 위의 빵을 떨어뜨립니다. 빵이 코드이고, 효자손으로 건드려서 떨어뜨릴 수 있다는 것은 쓸 수 있다는 의미입니다. 이 현상은 앞의 예시에서 개발자 C가 export를 추가해서 특정 패키지의 코드를 뽑아 쓴 것과 유사합니다.

 

 

경계의 중요성

NX를 개발하는 팀에서는 모노레포를 "잘 정의된 프로젝트 간의 경계, 잘 정의된 프로젝트 간의 조합"이라고 말합니다. 모노레포 내부의 프로젝트 간 경계가 잘 수립되지 않으면 모노레포의 이점을 얻기 어렵다는 말로도 해석할 수 있습니다. 모노레포의 형태를 가지고 있어도 경계가 제대로 정의되어 있지 않다면 모노레포가 아닐 수도 있다는 의미입니다.

 

잘 정의된 프로젝트 간 경계는 코드 간 참조할 수 있는 관계와 그렇지 않은 관계를 정의합니다. 코드베이스의 복잡도가 급격하게 상승하는 것을 막고 제품의 예측 가능성도 높이는 것입니다.

 

폴리레포로의 전환 결정

저희는 이러한 경계를 수립하기 위해서 모노레포를 폴리레포로 쪼개는 방식을 택했습니다. 모노레포를 유지하면서 경계를 수립하는 방식과 폴리레포로 가는 방식을 고민했는데, 모노레포를 해체함으로써 얻는 장점이 단점보다 많다고 판단했습니다.

 

경계를 만들 때 모노레포를 해체하는 것이 모노레포를 유지하는 것보다 리소스가 적게 들었습니다. 정기 배포를 빨리 끝내는 것이 가능했고, 레포를 쪼개서 자연스럽게 경계를 만들고 경계 내부의 안정성을 확보할 수 있었습니다. 경계 내에서는 어떤 문제가 생겨도 경계 밖의 코드베이스에 영향을 주기 어렵기 때문입니다.

 

단점으로는 모노레포 위에서 모든 PR들이 한 레포지토리에 올라와서 업무 공유가 편했던 것이 더 이상 되지 않는 점, 컨벤션을 강력하게 강제하는 방법이 떨어져서 코드가 파편화될 수 있다는 점, 그리고 원자적 커밋(모노레포에서 특정 커밋 몇 개만으로 전사가 일하는 방식이나 툴링에 영향을 미칠 수 있는 방식)이 더 이상 기능하지 않게 된다는 점이었습니다. 하지만 이런 단점들은 다른 방식으로 감당할 수 있으리라 생각했습니다.

 

 

챕터의 많은 개발자들이 경계가 주는 안정성에 공감했고, 모노레포를 해체하자는 의견으로 모아졌습니다. 모노레포 위에서 적절한 경계를 만드는 툴링은 정말 많은 비용을 필요로 합니다. 크면 클수록 더 그렇습니다. 반면 레포지토리를 쪼개면 이러한 툴링에 대한 리소스를 사용하지 않아도 되고 자연스럽게 경계가 만들어집니다.

 

구글은 수만 명이 개발하는 전사 모노레포를 유지하기 위해 정말 많은 자체 개발 도구들을 사용하고 있습니다. 모노레포의 강점을 유지하기 위해 막대한 인력과 리소스를 사용하고 있는 것입니다. 이는 모노레포 위에서 툴링으로 경계를 수립하는 것이 얼마나 쉽지 않은지를 보여줍니다.

 

2. 코드베이스를 복잡한 상태로 방치하지 말 것

사람이 인지할 수 있는 의존성과 버전의 개수는 한계가 있습니다. 플렉스 웹 제품 모노레포의 문제들은 유기적으로 연결되어 있었습니다. 크고 방치되어서 패키지와 의존의 개수가 개발자의 인지 범위를 넘어갔고, 의존성이 너무 많아서 기능 하나를 붙일 때도 알아야만 하는 패키지가 너무 많았습니다. 하위 패키지를 고칠 때 영향을 받을 상위 애플리케이션이나 패키지를 제대로 특정할 수 없었기 때문에 코드 변경이 어렵고 두려워졌습니다.

 

코드 변경이 두려워진다는 것은 개발팀 입장에서 엄청난 손해입니다. 이 때문에 팀의 속도가 느려지고 개발 문화까지 악영향을 받게 됩니다.

 

 

기여가 쉬운 코드베이스에서는 작업자가 어떤 코드를 변경하는지 정확히 알고, 비교적 안전한 환경에서 정원의 식물을 관리하듯이 물을 주고 가꾸며 코드를 개선하게 됩니다. 개발자는 효용감을 얻고, 자신이 기여한 공유 모듈들을 다른 분들이 써주면서 좋은 환경에서 일하게 됩니다. 이런 환경에서 식물도 무럭무럭 자라고, 좋은 공용 모듈들도 많이 만들어지며, 개발 조직의 생산성도 함께 올라갑니다.

 

그러나 복잡하고 방치된 모노레포에서는 강한 자들만 살아남을 수 있습니다. 알아야 할 것이 끔찍하게 많아집니다. 오래 근속해서 히스토리를 다 아는 사람들만 책임감 있고 자신감 있게 코드 변경을 할 수 있는 상황이 됩니다. 작은 기여도 모노레포의 역사와 절망의 소용돌이 속에서 불가능해지고, 아무도 그 큰 책임을 지고 싶지 않아서 공용 모듈에 기여하지 않습니다. 식물들이 죽고, 공용 모듈이 죽고, 개발 조직 전체의 생산성이 떨어지고, 공유하는 문화도 사라집니다.

 

패키지 정리 작업

정말 이 패키지가 다 필요할까 생각해보니 아니었습니다. 패키지가 너무 많아서 공유되는 코드는 많지만 해당 코드에 대한 사용률이 너무 적었습니다. 가시화된 적이 없어서 어디에 뭐가 있는지 공유가 안 되었고, 개발자분들이 개발할 때 "이런 공유 모듈 만드신 분 있나요?" 하고 수소문해서 공용 모듈을 사용하고 있었습니다.

 

실제로 공유되지 않은 패키지도 너무 많았고, 패키지가 너무 작게 나눠져서 export되는 구현체가 하나 두 개밖에 없는 경우도 많았습니다. 이런 패키지들은 정리하거나 없애는 것이 가능했습니다.

 

고구마(Goguma) 도구

패키지 개수를 줄이는 데 필요했던 것은 바로 고구마였습니다. 고구마는 모노레포의 복잡한 패키지 의존 관계를 추적하고 자동화된 리팩토링을 수행하는 도구입니다. 의존성이 고구마 줄기처럼 계속 나온다는 표현에서 이름을 착안했습니다.

 

 

모노레포 전체 의존성 현황을 CSV로 출력해서 의존성이 몇 개인지, 사용처는 몇 개인지 분석하고 현황을 계속 업데이트하면서 비슷한 패키지는 합치고, 공유되지 않거나 사용처가 없는 패키지들은 없앴습니다. 또한 패키지 수를 적극적으로 관리하기 위해 고구마 워크플로우를 만들어서 매주 월요일 아침에 패키지 개수 현황과 정리해야 될 패키지들을 슬랙 채널에 챕터 전체를 태그해서 알려주는 워크플로우도 만들었습니다. 이 스레드 밑에서 "이 패키지 정리해 주세요", "이 패키지는 왜 생겼나요?", "이게 뭔가요?" 같은 소통을 많이 했습니다.

 

고구마의 효과는 강력했습니다. 2024년 패키지 정리를 시작했던 2월 초 대비 6월에는 패키지 개수를 거의 절반에 가깝게 줄이는 데 성공했습니다. 정리하기 전에 패키지가 375개가 있었던 것입니다. 패키지 개수가 줄어들어서 전체 패키지 빌드에 걸리는 시간이 줄었고, 패키지 빌드를 돌리는 CI의 성능도 개선할 수 있었습니다.

 

 

통 버저닝(Bundle Versioning) 전략

패키지 개수를 줄여나갔지만, 여전히 남은 패키지들은 많았고 의존성은 복잡했습니다. 발표 초반에 보여드린 암흑의 우주 같은 의존성 그림을 예쁘게 역방향 참조 없는 트리 모양으로 정리하려면 아마 퇴사할 때까지 의존성만 뜯어도 못 할 것입니다. 열심히 정리해 봤자 개발이 진행되면 다른 쪽에서는 의존성을 복잡하게 만들기 때문에, 계속 정리하는 것도 효용이 없을 확률이 컸습니다.

 

 

결국 이 복잡한 의존성 덩어리를 몇 개로 나누고, 나눠진 작은 덩어리 사이의 참조 관계를 정의해야겠다는 생각을 하게 되었습니다. 패키지 여러 개를 하나의 버전으로 통으로 버저닝한다는 아이디어가 여기서 출발했습니다.

 

통 버저닝은 공통점을 가진 패키지 여러 개를 하나의 별도 레포지토리에 위치시키고, 패키지 여러 개를 하나의 버전으로 버저닝하는 것입니다. 하나의 버전을 가진 그룹 내에서는 패키지 간의 상호 참조를 허용하고, 같은 버전에서는 패키지 동작의 안전함을 보장하는 엔지니어링 정책입니다.

 

애플리케이션 레포지토리의 package.json 루트에는 flex-core-versions라는 플렉스팀 프론트엔드 챕터에서만 쓰이는 필드를 만들고, 거기에 3개 패키지 레이어의 버전을 관리합니다. 각 레이어에는 여러 개의 패키지들이 존재하는데, 이 패키지들의 의존 버전이 애플리케이션의 flex-core-versions 값과 일치하지 않거나 역방향으로 참조하면 의존성 설치와 CI 과정에서 에러를 발생시킵니다. 이런 방식으로 의존성과 버전 정합성을 강제했습니다.

 

 

인지 부하 감소

여러 개의 패키지를 하나의 버전으로 버저닝하는 정책을 통해서 처음으로 코드베이스의 의존 관계를 설명할 수 있게 되었습니다. 통으로 버저닝되는 패키지들의 그룹을 Packages, Design System, Services라는 3개의 레포지토리로 나누고, 해당 그룹 간의 의존 관계를 정의해서 Packages는 Design System에, Design System은 Services에, Services는 애플리케이션에 의존할 수 있게 단방향으로 의존되는 관계를 정의하고 강제했습니다.

 

패키지 레이어링 조치가 만드는 단순함에 대해 설명하면, 원래 모노레포에서 모든 패키지와 애플리케이션이 한 바구니에 있을 때 하위 패키지를 변경하면 의존 관계상 영향을 받는 패키지들의 전체 레벨이 6개였습니다. 패키지가 또 다른 패키지를 바꾸고, 그 패키지가 또 다른 패키지를 바꿀 때 거쳐 가는 트리 노드의 레벨, 즉 높이가 6이었습니다.

 

 

모든 패키지들이 의존성 수립이 자유로운 상태에서는 이 숫자가 10이 될 수도 있고 20이 될 수도 있습니다. 이 숫자가 늘어날수록 개발자가 변경을 염두에 두고 영향 범위를 생각하는 것이 몹시 어려워집니다.

 

레포지토리별 버저닝은 이 의존성 트리 레벨의 상한을 3으로 만들었다는 데 의미가 있습니다. 레포지토리 내부에 패키지 여러 개가 바뀌어도 개발자는 딱 3개의 의존성 레벨과 버전만 신경 쓰면 됩니다. 자연스럽게 개발자가 변경을 염두에 둬야 하는 범위가 적어지고 코드베이스를 이해하는 것이 훨씬 쉬워집니다.

 

 

이렇게 패키지 개수와 의존 관계를 정리하려고 했던 이유는 결국 프론트엔드 개발자분들의 인지 부하를 줄이기 위해서였습니다. 정리를 하고 난 뒤에야 코드의 구구절절한 역사와 사연을 알기보다는 버전과 몇 개의 패키지들의 역할에 대해서만 알고 있어도 개발이 쉽게 가능한 코드베이스를 만들 수 있었습니다.

 

저희는 이것을 한 번에 했는데 상당히 고통스러웠습니다. 팀과 제품이 빠르게 커지는 팀에서는 제품을 만드는 개발자들의 인지 부하를 어느 정도 관리하고 적어도 염두에 두실 수 있으면 좋을 것 같습니다.

 

3. 공유 모듈이 무엇인지 정의하고 알맞게 다뤄야 함

플렉스팀 모노레포에서 공유되는 모듈 중에는 이상한 것들도 있었습니다. 상품 재고 조회 리스트인데 재고가 떨어지면 재주문하는 버튼이 갑자기 들어가 있습니다. 속성 하나를 true로 바꾸면 갑자기 버튼이 나타납니다. 그리고 고객 만족도 조사 폼도 들어가 있습니다. 이것도 속성 하나를 true로 바꾸면 갑자기 나타납니다. true로 바꿔보기 전까지는 뭐가 나올지 모르는 공용 모듈이 된 것입니다.

 

이런 것들은 기능이 필요할 때마다 개발하고 붙여서 사용처가 늘었던 모듈들입니다. 이 정도가 되면 원래의 재고 조회 리스트는 더 이상 공유하기 힘들어집니다. 여러 가지 기능과 요구 사항이 붙어서 뭔지 모를 것이 되었습니다. 이렇게 난개발된 구현체는 기능이 제대로 가시화되거나 스펙이 제대로 정리되었을 확률도 낮기 때문에 공유 모듈로서 더 이상 선택되지 않고 기존 앱들과의 의존 관계만 간신히 유지하고 있습니다.

 

이러한 뭔지 모를 것들이 된 채 난개발된 공유 모듈들은 도메인의 경계를 침범합니다. 간단하게 package.json에 새로운 의존성 하나만 더 추가하면 해당 패키지가 의존하는 온갖 패키지들을 또 들고 들어갑니다. 이런 상황에서 의존성 트리 하위에 있는 또 다른 패키지가 바뀌었을 때 앱이 배포돼야 할 수도 있는 상황이 되어서 코드베이스 전체가 계속 취약해집니다.

 

공유 모듈의 기준 정립

코드베이스에 이런 공유 모듈로서의 가치가 떨어지는 코드가 많은 상황에서 저희 팀은 공유 모듈이 될 수 있는 코드에 대한 기준을 확실하게 정립했습니다.

 

첫 번째로, 제품 전체에서 하나여야만 하는 코드입니다. 디자인 일관성을 보장하기 위한 디자인 시스템, 그리고 명확한 기획과 스펙이 있어서 추후 변경의 가능성이 적은 비즈니스 로직, 이를 포함하는 UI 컴포넌트들이 포함됩니다.

 

두 번째는 공용화가 안 되면 개발 생산성이 급격히 떨어지는 코드들입니다. 저희가 쓰고 있는 마이크로 프론트엔드 프레임워크나 프레임워크성 기반의 도구들이 이에 해당됩니다. 이에 해당되지 않는 다른 공유 레이어의 코드들은 사용처가 몇 개든 각 사용처로 중복을 감수하며 의존 관계를 정리했습니다. 개발자들이 주로 DRY 원칙(Don't Repeat Yourself) 즉 중복을 없애는 방식을 많이 고민하는데, 이 정리 작업에는 완전 반대 접근만이 유효했습니다.

 

통 버저닝되는 패키지 그룹 3개(Packages, Design System, Services)가 공유 모듈이 될 수 있는 조건을 충족하고 있었습니다. Packages는 유틸성 도구와 프레임워크, Design System은 말 그대로 디자인 시스템, 그리고 Services는 변경점이 하나여야만 하는 비즈니스 로직에 해당합니다.

 

의사결정 프레임워크

 

웹 클라이언트 플랫폼 팀에서는 이 공유 모듈들을 바탕으로 세 개의 레포지토리에 들어갈 수 있는 것이 공유 모듈인지, 혹은 그럴 가치가 없어 사용처로 내재화해야 하는 코드인지를 판단해야 했습니다. 이를 잘 판단하기 위해 다음과 같은 기준을 세우고 의사결정 틀을 만들어 팀에 공유하고 이 원칙대로 공유 모듈을 정리해 나갔습니다.

 

공유 모듈이 가치 있는 스펙이라고 볼 수 있는가? 변경될 가능성이 높은가? 변경점을 하나로 만들어야만 하는가? 이런 질문들을 평가하고, 그렇지 않다면 코드를 중복시켰습니다. 공유 레이어로 들어가야 하는 코드들조차도 어떤 구현체 몇 개만 뽑아서 들어갈 수 있는지, 혹은 패키지 전체가 들어가야 하는지를 평가해서 최대한 공유 레이어로 가는 코드들을 줄이려고 노력했습니다.

 

코드 고립 작업

실질적으로 이 공유 모듈 정리 작업이 어떤 맥락에서 어떻게 진행되었는지 설명하면, 모노레포의 애플리케이션을 별도 레포지토리로 뜯으려면 결국 분리하고자 하는 애플리케이션들이 별도 레포지토리로 뜯을 수 있을 정도까지 고립되어야만 했습니다. 코드가 모두 고립될 수 있어야 모노레포 위에서 그것을 뗄 수가 있었습니다. 코드 고립 상태를 만들기 위해서는 개별 레포지토리로 뜯고자 하는 코드에 의존하는 모든 코드가 다른 애플리케이션과 의존을 하는 경우, 그 의존성을 어떻게든 끊거나 그 패키지를 공유 레이어로 이동시켜야 했습니다.

 

이 코드 고립 상태를 만들고 레포지토리를 떼어내는 작업이 굉장히 지난했습니다. 특정 모듈을 패키지에서 다른 패키지로 옮기고, 의존성 끊고, import문 바꾸고, 모듈 내재화해서 14만 줄짜리 변경을 만들고, 컨플릭트 풀고, 기가 막힌 순환 참조도 해결해야 했습니다. 죄송하다고 양해도 많이 구해야 했고, 팀원분들의 고통이 상당했습니다.

 

 

작업 가시화와 관리

크고 지난한 일일수록 제대로 계획을 짜고 선명하게 가시화해서 진척도를 체크하면서 앞으로 나아가야 했습니다. 코드 고립 작업 과정에서 정말 큰 역할을 한 것이 작업 계획서였습니다. 분리된 레포지토리별로 작업 계획서를 작성했습니다. 테이블은 작업 진척도와 작업 완료일을 나타냈고, 개별 레포지토리 하나마다 작업 단계를 수행하고 표시해서 작업이 얼마나 진척되고 있는지 자세히 표현하려고 했습니다.

 

각 레포지토리의 작업 계획서 안으로 들어가면 작업 대상 구현체 표가 있었습니다. 이 표는 소스 코드를 분석하여 특정 애플리케이션에 위상적으로 의존하는 모든 구현체를 리스팅한 것입니다. 각 레포지토리를 분리하기 위해 작업할 구현체들에 대한 목록을 미리 모두 파악하여 표시해 놓고, 조치 완료되었을 때 체크하는 방식으로 진척도를 보였습니다.

 

이 구현체들 중 조치가 필요한 것들은 모두 의사결정 틀을 거쳐 공유 레이어로 가거나 코드 중복을 감수하고 내재화되어 조치 완료 상태로 이행했습니다. 구현체 하나하나의 노션 문서를 살펴보면, 특정 구현체가 함수인지 변수인지 React Hook인지, 어떤 패키지에 있는지, 어떤 애플리케이션에 위상적으로 의존 관계가 있는지, 어떤 애플리케이션을 직접 사용하는지에 대한 데이터를 모두 미리 파악했습니다.

 

이 정도로 치밀하게 가시화를 해놓으니 프로젝트에 대해서 이야기할 때도 좋았고, 진척도가 높은 해상도로 눈에 잘 보여서 긴 프로젝트를 수행하는 팀이 지치지 않을 수 있었습니다. 진척도를 수시로 확인하면서 프로젝트를 투명하게 진행할 수 있었습니다.

 

 

마치며

지금까지 정기 배포 그만두기 프로젝트를 진행하며 배운 내용을 공유했습니다. 팀과 제품에 맞는 적정 코드베이스를 만들기 위해 알아야 하는 세 가지는 다음과 같습니다.

 

첫째, 경계 없이 코드를 공유하지 말 것. 

둘째, 코드베이스를 복잡한 상태로 방치하지 말 것. 

셋째, 공유 모듈을 정의하고 알맞게 다뤄야 한다.

 

통 버저닝과 패키지 개수 감소 같은 조치들은 안정성을 확보하기 위해 어쩔 수 없이 했던 것들입니다. 규제가 빡빡한 편인데, 이 규제들을 풀고 스쿼드에서 조금 더 툴링이나 개발 환경들을 자유롭게 가져갈 수 있도록 코드베이스 전체가 이행할 수 있도록 준비하고 있습니다. 공유 모듈이 무엇이고 어디에 있어야 하는지 정의를 처음으로 했기 때문에, 이 공유 모듈과 디자인 시스템 코드들에 대한 사람과 LLM이 모두 이해할 수 있는 명세를 제공할 예정입니다. 이 명세를 MCP 등으로 접근 가능하게 한다거나 하는 방식으로 생산성 향상에 기여할 수 있도록 웹 클라이언트 플랫폼 팀의 리소스를 집중할 계획입니다.

 

정기 배포 그만두기 프로젝트가 웹 클라이언트 플랫폼 팀을 정의했다고 생각합니다. 3년 정도 지나면 익숙해져서 바꿀 필요성도 안 느껴지는 상황에서도, 저희 팀은 "이렇게 배포하는 게 맞을까", "이렇게 고통을 받으면서 개발하고 있는 게 맞을까" 하는 의문을 제기하면서 이 프로젝트를 진행해 왔습니다. 정기 배포를 끝내고 나서야 전사가 알게 되었습니다. 이것이 너무 불편한 것이었고, 제품이 전달되는 말단의 가장 큰 병목이었다는 것을 말입니다. 

 

이 프로젝트를 진행하면서 전사가 일하는 방식까지 바꿨습니다. 특히 프론트엔드 챕터가 모노레포에서 일하는 것이 걱정스러웠기 때문인데요. 개발이 지난하고 힘들어서 퇴사하거나, 개발이 싫어질까 봐 걱정을 많이 했습니다. 그러나 다행히 프로젝트가 잘 진행되어, 이제는 불타는 절망 모노레포에서 해가 쨍쨍 비치는 맑은 땅으로 나아갈 수 있게 됐습니다.

 

©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.