go to post list

목 말라 Continuous 줘

2023. 12. 02 • 개발자의 멋진 협업을 위해 시도해본 것들

1. 좋은 개발 환경에 대한 목마름

대망의 마지막 프로젝트가 시작됐습니다. 마지막 프로젝트에서 꾸려진 팀에서는 팀 구성원이 프로젝트 기획이나 구현보다, 프로젝트 관리 측면의 경험에 대한 갈망이 있었습니다. 모두가 초심자라는 점과 나중에 발견되는 헛점, 컨벤션이 없을 때의 소통 미스와 배포된 어플리케이션이 다운되는 상황 등으로 프로젝트를 진행하면서 불편을 너무 많이 겪었던 탓입니다. 프로젝트 초기에 기획 회의를 하면서 이런 것들을 실천해보자는 공감대가 확보되었고, 공통적으로 강하게 필요성을 느끼는 것들은 아래 세 개로 좁혀졌습니다.

  1. 코드 리뷰
  2. TDD
  3. CI/CD 환경 구축

프로젝트를 기술 스택 연습이나 기능 구현을 해 보는 선에서 끝내지 않고, 지속 가능한 개발환경을 구축해 깔끔한 협업 환경을 경험하고 싶다는 마음이었다고 생각합니다. 사실 이런 걸 하려고 이것저것 기술을 도입하는 게 겉멋만 든 거라든가 허세나 욕심이라고 생각하실 수도 있는데 그냥 프로젝트를 두 번 해 보니 협업 측면에서 답답한 마음이 너무 컸었던 것이라고 이해해 주시면 좋겠습니다. 아무튼 첫 번째 프로젝트에서는 수동 배포를 해서 배포 환경에서 결과물을 확인하고 싶을 때마다 담당자가 일일히 배포 커맨드를 써서 배포해 줘야 했고요, 두 번째 프로젝트에서는 프론트엔드와 백엔드 코드 중 하나라도 수정되면 나머지 모두가 빌드되어서 시간이 10분 이상씩 걸리는 문제가 있었습니다. 또 두 프로젝트에서 테스트 코드를 써 본 경험이 없었습니다. 프론트엔드의 경우 Storybook 은 사용하고 있었지만 제대로 사용했다기보다는 눈으로 구현 여부를 확인하거나, 케이스 몇 개를 확인할 수 있도록 하는 정도에 그쳐 있었고요. SSAFY의 프로젝트는 코드 퀄리티보다는 기획이나 아이디어에 비중이 있는 편입니다. 저희는 개발자니까 프로젝트마다 개발이나 코드 품질에 집중해 보자고 했지만… 회고를 해 보니 그 분위기에 저희도 휩쓸리고 있었던 것 같았습니다. 이번에 주어진 시간은 기존 프로젝트들보다 1주 짧은 6주였습니다. 그래서 기획을 과감하게 쳐내고 하고 싶었던 것들을 그냥 다 해 보자는 쪽으로 의견이 모아졌습니다.

2. 우리가 해볼 수 있는 것

앞선 두 번의 프로젝트에서 구축된 개발 환경을 보면서 개선하고 싶은 점들이 있었습니다. 특히 Module Federation이 적용된 두 번째 프로젝트에서, 한 개의 프로젝트만 수정되었는데 모든 프론트엔드 프로젝트를 다시 빌드하는 것을 보고 이 문제를 해결할 방법이 있을까 하는 생각을 했습니다. 두 번째 프로젝트가 기술 도입으로 주어진 과제를 해결하는 과정이었다면, 세 번째 프로젝트에서는 한 걸음 더 나아가 과제를 해결하는 개발자의 개발 환경을 개선하는 경험을 해 보고 싶었습니다. 첫 번째 프로젝트에서 yarn berry 를 도입해 프론트엔드 빌드 타임을 대략 45초 줄인 경험이 있는데요, npm install을 하지 않아 절약된 시간은 생각보다 크게 다가왔었습니다.

강력한 코드 리뷰, TDD, 그리고 모노레포monorepo의 도입은 사실 하나의 생각에서 나온 것이라고 생각합니다. ‘안정감 있는 개발환경 구축’이 그것입니다. 기술적으로 미숙한 저희에게 6주, 7주 사이즈의 프로젝트는 모든 문제의 해결 과정 그 자체가 변수가 됩니다. 디버깅은 느리고, 시간은 부족한데, 하고 싶은 것은 많기 때문이에요. 이런 환경에서 프로젝트의 결과물, 즉 회사가 사용자에게 전하고 싶은 가치를 빠르게, 그리고 자주 전달하려면 개발자들이 배포를 할 때 불안해서는 안 된다고 생각했고, 그 중요한 단추가 개발 환경이라고 생각했습니다.

CI/CD는 제가 생각하기에 개발 환경의 총체입니다. 개발 환경은 기술의 영역이기도 하지만 절차의 영역이기도 하기 때문에, 프로젝트를 진행하면서 시간이 없고 개개인의 역량이 부족할 때 가장 먼저 타협하게 되는 영역이었습니다. 그렇기 때문에 실습해볼 기회가 별로 없는 영역이기도 하고요. 이번 프로젝트에서 프론트엔드는 초기 컴포넌트를 개발한 이후에는 가능한 한 도메인별로 프로젝트를 분리해 개발하기로 했고, 그렇게 개발했을 때에도 컨벤션과 UI가 통일성을 갖춘 모습을 보고 싶었습니다. 그래서 모듈마다 수동으로 컨벤션과 의존성을 설정했던 두 번째 프로젝트에서와는 달리, Module Federation과 함께 모노레포 구축 툴을 사용하는 것을 목표 중 하나로 삼았습니다. 저희는 많은 모노레포 구축 툴 중 pnpm - turborepo 조합을 주목했는데요, 2022년 stateofjs.com 기준으로 사용량은 낮았지만 점차 상승하고 있었고, Retention이 가장 높았기 때문입니다. 가장 많이 사용하고 있던 Lerna 의 수치들이 점점 하락하고 있었고요.

retention:  다시 사용할 것 / (다시 사용할 것 + 다시 사용하지 않을 것)

retention: 다시 사용할 것 / (다시 사용할 것 + 다시 사용하지 않을 것)

Usage:  (다시 사용할 것 + 다시 사용하지 않을 것) / 전체 응답

Usage: (다시 사용할 것 + 다시 사용하지 않을 것) / 전체 응답

3. 툴 선택

프로젝트 규칙상 프로젝트 레포지토리가 한 개로 관리되어야 했기 때문에 강제적으로 모노레포 구조를 취하게 되었지만 사실 기본적으로 백엔드와 프론트엔드, 그리고 그 안에서도 모듈마다 레포지토리를 분리하고 싶다는 생각을 하고 있었습니다. 그리고 그게 자연스럽다고 생각했고요. 사실 Git에 미숙할수록 레포지토리를 분리해서 얻는 이점이 큽니다. 프론트엔드에서 conflict를 해결하는 과정에서 백엔드 코드가 하루 전으로 돌아가버린다든가 하는 일들이 일어나기 때문입니다.

모노레포는 이렇게 멀티레포(또는 polyrepo)로 관리되어야 하는 많은 프로젝트를 하나의 레포지토리 안에서 관리하는 개념입니다. 프로젝트 사이에 참조 관계가 생기거나, 공통으로 가지고 있어야 하는 코드들을 묶어내 일관성을 유지할 수 있는 장점이 있습니다. 모듈화가 필수적이기 때문에 확장성이 좋다고도 생각합니다. 각 도메인이 모듈로 관리되기 때문에 변경 사항을 잘게 쪼개 배포할 수도 있고, 전체 프로덕트의 버전 관리를 한 번에 할 수도 있을 것입니다. (당연히 관리를 못 하면 이게 단점으로 적용될 수도 있습니다)

일단 프로젝트를 스캐폴딩해야 했기 때문에 툴을 빠르게 골라야 하는 상황이었습니다. 모든 툴을 사용해볼 수 없어 빠르게 찾아본 후, 잘 정리된 글을 읽고 pnpm-turborepo 조합을 그대로 사용하기로 했습니다. 기본적인 설정이 필요없다는 점이 저희에게 최적의 조건이라고 생각했기 때문입니다.

4. 터보레포 사용기

저희는 터보레포와 함께 Next.js 앱을 도메인별로 쪼개 개발했습니다. (아키텍처 구조에 대해서는 이 글의 통신 과정을 보시면 이해하실 수 있습니다)

turborepo는 몇 가지 특징을 가지고 있는데, 저희가 구축한 환경에서 주요하게 드러났던 점은 다음 세 가지였습니다. (자세한 장점들은 공식 문서에 잘 나와있습니다.)

1. Content-aware hashing + Incremental builds

타임스탬프 기준이 아닌 파일 내용을 기준으로 해싱하기 때문에 이미 변경된 파일만 빌드합니다. 또 이미 빌드된 패키지는 캐시해놓았기 때문에 내용이 변경되지 않았다면 다시 빌드하지 않습니다. 로컬 환경에서 저희 프로젝트는 아래와 같이 빌드됩니다.

프로젝트를 최초로 빌드할 때 (또는 4개의 메인 패키지가 모두 변경되었을 때)

프로젝트를 최초로 빌드할 때 (또는 4개의 메인 패키지가 모두 변경되었을 때)

4개 중 하나의 메인 패키지만 변경되었을 때

4개 중 하나의 메인 패키지만 변경되었을 때

프로덕션 코드가 수정되지 않았을 때

프로덕션 코드가 수정되지 않았을 때

2. Parallel execution + Task pipelines

모든 코어를 사용해 최대한 병렬 처리를 하여 빌드 작업을 진행하며, 빌드 태스크 간의 관계를 정의한 다음 다음 작업을 언제 실행할지 최적화합니다.

패키지간의 의존 관계를 파악해 빌드 선후 순서를 계산함은 물론, 태스크간의 순서도 최적화합니다.

패키지간의 의존 관계를 파악해 빌드 선후 순서를 계산함은 물론, 태스크간의 순서도 최적화합니다.

3. Cloud Caching

빌드 파일을 클라우드에 업로드해 CI/CD 환경에서 공유, 빌드 캐싱을 지원합니다. 저희는 도커 환경에서 빌드되도록 인프라를 구축했기 때문에 빌드 캐시를 올리는 간단한 서버를 구현했습니다. 빌드 캐시가 적용되지 않는다면 모든 빌드가 7분 30초 정도의 시간이 걸렸겠지만, 패키지 하나만 수정된다면 3분 20초까지 빌드 시간이 감소합니다. (SSAFY의 환경에서는 모든 모듈을 빌드할 때 6분, 하나만 빌드한다면 1분까지 감소합니다)

5. 후기

turborepo에 대해 공부하고 사용해 보면서 느낀 점은, “나도 언젠가 저런 걸 만들고 싶다”는 것이었습니다. 비단 모노레포 관리 툴이나 패키지 빌드 툴이 아니더라도 사용자의 어떤 문제를 해결할 수 있는 도구를 만들고 싶었었다는 걸 깨달았습니다. 또 당연히 어떤 새로운 기술을 도입하면 이런저런 문제가 발생합니다만, 가장 큰 문제는 배포 관련 문제였습니다. 일반적인 상황이 아니었기에 인프라 담당자도 프론트엔드의 프로젝트 구성 상황에 대해 새로 학습을 해야 했습니다. 저희의 프로젝트 배포는 도커 환경에서 이루어졌고, 로컬 환경과 달라 전역 락파일로 인한 의존성 재설치 문제와 원격 빌드 캐시 미적용 등의 문제가 있었습니다...만 고맙게도 인프라 담당자가 turborepo에 대해 따로 학습하고 빌드 캐시 서버를 따로 구축하는 등 많은 노력을 해 준 덕에 도커 환경에서도 빌드 시간 감소로 인한 쾌적함을 체감하면서 개발할 수 있었습니다. 이 당시 저희 프로젝트는 크게 Next.js App 4개로 이루어져 있었는데요, 저희 프로젝트의 아키텍처 이야기는 나중에 다른 글에서 소개해 보겠습니다.

모노레포를 제대로 다뤄봤다고 하기는 어렵겠지만, 여러 패키지를 분리함으로써 얻는 이점들에 대해서는 체감을 해 보았습니다. 프로젝트 관리의 쾌적함, 공통된 의존성과 컨벤션 관리, 그리고 터보레포의 캐시를 통한 빌드 시간 단축은 CI/CD 환경이 얼마나 중요한 것인지에 대해 다시 한 번 알 수 있었던 경험이라고 생각합니다. 다만 프론트엔드 인원이 2명이었다는 점, 시간 부족과 역량 부족으로 테스트 코드와 서비스 구현을 많이 포기했다는 점이 많이 아쉽게 다가오는 프로젝트였습니다. 처음 다뤄보는 기술스택들이 한가득이었고, 그것들을 왜, 어떻게 사용하는 것인지 익히는 것만 해도 많은 시간이 필요한데 제 욕심에 부응해 주고 또 같이 욕심이 많아줘서 고마웠던 프론트엔드 동료와 인프라 담당자에게 이 글을 빌려 깊은 감사를 보냅니다.

또, 확실히 처음 다뤄보는 도구들을 많이 도입한 프로젝트는 처음 보는 에러도 많이 마주하게 됩니다. 생전 처음 보는 에러 메시지부터 여러 문제들을 많이 겪었는데요, ‘이 문제는 이렇게 해결했었는데 이것과 연결해서 풀 수 있지 않을까?’ 하는 경험으로 쌓인 직관과, 그 동안에 쌓인 지식으로 세운 원인에 대한 가설들을 검증하는 과정들이 빛을 발하는 시간이었던 것 같습니다. 개발 공부를 시작한 뒤로 내가 개발자 할 수 있는 게 맞나 하는 생각을 여러 번 했지만 아무래도 무언가를 만드는 재미와 살아있다는 느낌, 문제를 풀어낼 때의 성취감 때문에 계속하고 있는 만큼 괜찮은 선택이었다는 생각도 많이 합니다.

다른 글