<p style="text-align:justify;">국내 유명 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하는 걸까요?</p><p style="text-align:justify;"> </p><p style="text-align:justify;">이번 글은 ‘성과 극대화를 위한 머신러닝. 모바일 마케팅 성과분석 솔루션’ 에어브릿지(airbridge.io)를 만드는 AB180 Airbridge API 팀의 이야기입니다. 다양한 컴포넌트를 다루면서 생긴 문제점을 Monorepo로 해결한 경험을 공유합니다.</p><div class="page-break" style="page-break-after:always;"><span style="display:none;"> </span></div><h3 style="text-align:justify;"><strong>Monorepo 도입 배경</strong></h3><p style="text-align:justify;">SaaS를 만들어 고객에게 제공하는 과정에서 한 개로 시작한 컴포넌트가 필요에 따라 여러 개로 나누어져 관리하는 경우를 흔히 볼 수 있습니다. 이렇게 나누어진 컴포넌트 사이에는 연관관계가 생기고, 때로는 하나의 컴포넌트가 다른 컴포넌트에 의존성이 생기기도 합니다. AB180의 Airbridge API 개발팀에서도 다양한 컴포넌트를 다루고 있고, 이 과정에서 겪었던 몇 가지 문제점을 Monorepo를 통해 해결한 경험을 소개하고자 합니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>Monorepo란?</strong></h4><p style="text-align:justify;">Monorepo는 하나의 저장소에서 여러 프로젝트를 관리하는 것을 의미합니다. 각각의 프로젝트들은 기존에 사용하던 환경처럼 각각 독립된 개발 환경을 가질 수 있습니다. 다시 말해, 같은 저장소에 속한 프로젝트들이 동일한 개발환경을 구성하고 있어야 하는 것은 아닙니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>도입 배경</strong></h4><p style="text-align:justify;">앞서 말씀드린 것처럼 Airbridge API 개발팀은 다양한 컴포넌트를 관리하고 있으며, 각 컴포넌트는 독립된 저장소를 가지고 있습니다. 이 중 api, dashboard, utils 컴포넌트를 예시로 들어보겠습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image001.png" alt="도입 배경"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">utils 컴포넌트는 다른 여러 컴포넌트에서 사용되는 모델, 로직 등의 코드를 공통으로 사용하고자 만들어둔 패키지 형태의 컴포넌트입니다. 해당 컴포넌트는 api, dashboard 컴포넌트에서 사용되고 있습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image003.png" alt="utils 컴포넌트"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">utils 컴포넌트에 있는 코드를 업데이트하고 api, dashboard에 반영하기 위해서는 다음과 같은 과정을 밟아야 했습니다. 먼저 utils 컴포넌트에서 발생한 변경점을 새 브랜치에 commit하고 utils 저장소에 push하고 pr을 만든 뒤 master에 merge하고, github에서 새로운 버전을 release합니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">api와 dashboard 컴포넌트에서 새로운 버전의 utils를 반영하고, 발생한 변경점을 새 브랜치에 commit하고 각 저장소에 push하고 pr을 만든 뒤 master에 merge합니다. 만약 추가로 utils에 변경이 필요하다면 보기만 해도 복잡한 이 과정을 다시 한번 진행해야 합니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="margin-left:0px;text-align:justify;"><strong>작업 목표</strong></h3><p style="text-align:justify;">기존 개발 패턴을 해치지 않으면서 위에서 보신 것처럼 복잡한 업데이트 과정을 단순화하여 생산성을 높이고, Airbridge API 팀에서 사용하는 여러 컴포넌트를 한 번에 관리할 수 있게 가시성을 개선하고자 했습니다. 그리고 utils처럼 다른 컴포넌트에 영향을 줄 수 있는 경우, 연관된 컴포넌트까지 일관성을 유지할 수 있게 테스트와 배포까지 잘 수행되는 것을 목표로 삼았습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>저장소 합치기</strong></h4><p style="text-align:justify;">Airbridge API 팀에서 사용하는 컴포넌트를 api 저장소로 합치는 작업을 진행했습니다. 루트 디렉터리를 기준으로 프로젝트를 구분하는 형태로 Monorepo를 구성했습니다. 루트 디렉터리에 api, dashboard, utils 폴더를 만들고 각 컴포넌트의 소스를 옮겼습니다. 패키지 형태로 사용하던 utils 컴포넌트의 경우에는 api, dashboard에서 로컬 패키지 형태로 사용하도록 수정했습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image005.png" alt="airbridge github"></figure><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>CI/CD 연결</strong></h4><p style="text-align:justify;">각 컴포넌트는 Github Webhook을 이용하여 Codebuild와 연동되어 있고, PR CI, Merge 생성 시 CD가 이루어지는 자동화된 CI/CD가 구성되어 있고, PR에서 CI의 결과를 직접 확인할 수 있습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image007.png" alt="자동화 ci/cd"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">Monorepo가 적용된 환경에서도 각 컴포넌트의 CI/CD는 기존처럼 별개로 동작하는 것을 목표로 했기에, 각 저장소에 연결되어 있던 Github Webhook들을 Monorepo인 api 저장소로 이동시켰습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image009.png" alt="github webhook"><figcaption>api 저장소에 연결된 컴포넌트 CI/CD용 Webhook</figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">git diff 정보를 이용하여 해당 컴포넌트의 변경점이 있거나 연관된 컴포넌트의 변경점이 발생한 경우에만 CI/CD를 수행할 수 있도록 각 컴포넌트의 Codebuild 스크립트를 변경합니다. 이렇게 하면 api 저장소에 PR이 생성될 경우 CI/CD가 실행되지만, 변경점이 컴포넌트에 없으면 실제 CI/CD 과정을 밟진 않습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image011.png" alt="api 컴포넌트"><figcaption>api 컴포넌트의 경우 api, utils의 변경점이 있을 때 CI/CD를 수행한다.</figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">Monorepo를 적용한 상태에서 utils 컴포넌트를 업데이트하는 과정을 살펴보겠습니다. 로컬 패키지를 사용하고 있기에 utils 컴포넌트의 코드를 변경하고 새 브랜치를 만들고 api 저장소에 utils, api 변경점을 한 번에 commit, push, pr, merge 하는 과정을 밟으면 업데이트 과정이 완료됩니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">Multirepo를 사용할 때 보다 훨씬 간단해진 것을 체감할 수 있습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>작업 완료 이후 발견한 문제</strong></h3><p style="text-align:justify;">각 컴포넌트의 CI/CD 수행 여부를 CI/CD 과정에서 직접 판단하도록 설계하면서 발생한 몇 가지 문제를 먼저 살펴보겠습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>1) 복잡해진 CI/CD 스크립트</strong></h4><p style="text-align:justify;">CI/CD의 필요 여부를 판단하고 이에 따라 분기하는 로직이 추가되어 Codebuild 스크립트가 복잡해졌고, 본연의 역할인 CI/CD를 넘어 더 많은 역할을 책임지게 되었습니다. 그리고 컴포넌트의 연관관계를 변경될 때도 Codebuild 스크립트를 변경해야 합니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>2) CI/CD 성공 처리 문제</strong></h4><p style="text-align:justify;">생략된 CI/CD의 경우에도 성공으로 처리되어 종료됩니다. 정상적으로 수행되어 성공으로 처리된 것인지, 생략하여 성공으로 처리된 것인지를 판단할 수 없었습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>3) 같은 코드 재배포 불가</strong></h4><p style="text-align:justify;">모종의 이유로 특정 컴포넌트의 재배포가 필요한 경우가 있습니다. 하지만 같은 코드를 배포할 경우 이미 동일한 코드가 배포된 상태이므로 변경점이 없다고 판단하여 CD를 수행할 수 없습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>4) 관리 포인트의 증가</strong></h4><p style="text-align:justify;">api 저장소에 새로운 컴포넌트를 추가로 합치거나 기존 컴포넌트를 제거할 때 CI/CD용 Github Webhook을 추가하는 것도 신경 써야 했습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>해결하기 위해서</strong></h3><p style="text-align:justify;">앞서 말씀드린 문제점을 근본적으로 해결하기 위해서는 CI/CD Codebuild가 실행되기 전에 각 컴포넌트의 CI/CD 수행 여부를 판단할 수 있는 무언가가 필요했습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image013.png" alt="컴포넌트 ci/cd 수행 여부"><figcaption>Code Deployer의 탄생</figcaption></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">Code Deployer라고 이름을 지은 새로운 컴포넌트를 만들어서 CI/CD가 수행될 수 있도록 과정을 개선했습니다. Code Deployer는 PR이 생성되거나 PR의 상태가 변화될 때 Github Webhook을 통해 Trigger 됩니다. PR의 브랜치와 master 브랜치 사이의 코드 변경사항을 추적하여 각 컴포넌트의 CI/CD가 필요한지 판단하고, 필요 시 CI/CD를 실행하는 역할을 합니다. Code Deployer의 도입으로 앞서 언급했던 문제를 해결할 수 있었습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>1) 복잡해진 CI/CD 스크립트 해결</strong></h4><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image011.png" alt="ci/cd 스크립트"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">각 컴포넌트의 Codebuild 스크립트에서 CI/CD 수행 여부를 판단하기 위해 추가했던 로직을 제거했습니다. 또한 컴포넌트의 연관관계를 지정하는 project_diff.txt 파일을 각 컴포넌트에 추가하여 관리할 수 있게 함으로써, 연관관계 설정을 위해 스크립트를 변경해야 하던 것을 해결했습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>2) CI/CD 성공 처리 문제 해결</strong></h4><p style="text-align:justify;">Code Deployer가 각 컴포넌트의 CI/CD 필요 여부를 확인하고, 불필요한 CI/CD는 수행하지 않게 만들어 실제로 성공한 CI/CD 결과만 기록될 수 있게 되었습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>3) 같은 코드 재배포 불가 해결</strong></h4><p style="text-align:justify;">CI/CD 과정에서 배포가 필요한지 판단하는 로직을 제거함으로써 자연스럽게 이 문제도 같이 해결되었습니다. 만약 재배포가 필요하다면 CD용 Codebuild를 Trigger하면 같은 코드라고 하더라도 정상적으로 배포가 이루어집니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>4) 관리 포인트의 증가 해결</strong></h4><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image017.png" alt="관리 포인트 증가"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">Codebuild와 직접 연결되어 있던 Github Webhook을 모두 제거하고, Code Deployer와 연결되는 Github Webhook을 추가했습니다. Code Deployer가 저장소 내의 모든 컴포넌트에 대한 CI/CD 실행을 담당하게 되면서 컴포넌트의 추가/제거 시 Github Webhook에 대해 신경 썼던 부분도 해결됐습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>조금 더 잘하기 위해서</strong></h3><h4 style="text-align:justify;"><strong>PR에서 CI 상태 확인</strong></h4><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image019.png" alt="ci 상태 확인"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">Code Deployer가 Codebuild를 실행하도록 변경되더라도 기존처럼 각 컴포넌트의 CI 결과를 PR에서 확인할 수 있도록 Github Checks API를 활용하여 구현했습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>생략된 CI도 확인 가능</strong></h4><p style="text-align:justify;">변경점이 없는 컴포넌트의 경우 CI/CD를 진행할 필요가 없으므로 Skip 했고, 이에 대한 가시성을 확보했습니다.</p><p style="text-align:justify;"> </p><h4 style="text-align:justify;"><strong>Slack에서 CI/CD 상태 확인</strong></h4><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image023.png" alt="슬랙 ci/cd 확인"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">기존 Codebuild 알림에서는 PR이나 CI 진행 상태가 한정적이었기에 이를 의미 있게 활용하기 어려웠습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:80%;"><img src="https://yozm.wishket.com/media/news/1487/image025.png" alt="codebuild 알림"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">누가, 무엇을 배포한 것인지 PR 정보를 알 수 있게 추가하고, 작업자가 CI 완료 알림을 받을 수 있게 해 알림의 가시성을 챙겼습니다.</p><p style="text-align:justify;"> </p><figure class="image image_resized" style="width:100%;"><img src="https://yozm.wishket.com/media/news/1487/image027.png" alt="알림 가시성"></figure><p style="text-align:justify;"> </p><p style="text-align:justify;">또한 작업이 프로덕션에 반영된 경우 따로 알림을 보내주도록 했습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;">이러한 과정을 거치면서 개선하고자 했던 목표를 달성했고, 생산성 향상을 위한 추가 기능 구현까지 완성하며 Monorepo 도입 작업을 완료할 수 있었습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>Monorepo 도입 이후 장단점</strong></h3><blockquote><h4 style="text-align:justify;"><strong>좋았던 점</strong></h4></blockquote><p style="text-align:justify;">Airbridge API 팀에서 관리하는 여러 컴포넌트를 하나의 저장소에서 한눈에 확인할 수 있게 되었습니다. 여러 컴포넌트에 해당하는 티켓을 작업할 때 관련된 변경점을 하나의 PR에서 확인할 수 있게 되었습니다. utils와 같은 패키지 형태의 컴포넌트도 하나의 저장소에서 관리할 수 있게 되어 생산성을 향상시킬 수 있게 되었습니다.</p><p style="text-align:justify;"> </p><blockquote><h4 style="text-align:justify;"><strong>아쉬웠던 점</strong></h4></blockquote><p style="text-align:justify;">컴포넌트별로 코드 접근 권한을 제어할 수 없다는 것과 로컬 패키지로 사용하는 컴포넌트들을 각각 다른 버전으로 사용할 수 없는 것이 아쉬웠습니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"> </p><h3 style="text-align:justify;"><strong>마치며</strong></h3><p style="text-align:justify;">Airbridge API 팀은 Monorepo 도입과 같이 팀원들의 생산성 향상과 보다 나은 개발 환경을 끊임없이 고민하고 있습니다. 다양한 컴포넌트를 다루는 다른 회사의 개발팀에서 Monorepo 도입을 고려할 때 이 글이 조금이나마 도움되기를 기대합니다.</p><p style="text-align:justify;"> </p><p style="text-align:justify;"><원문></p><p style="text-align:justify;"><a href="https://engineering.ab180.co/stories/airbridge-api-monorepo">Airbridge API 개발팀의 Monorepo 도입기</a></p><p style="text-align:justify;"> </p><p style="text-align:center;"><span style="color:#999999;">©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.</span></p>