모두싸인 프론트엔드 팀에서 개발에서 배포까지 하는 방법

Taehwan Noh
Modusign
Published in
9 min readOct 7, 2020

--

모두싸인 프론트엔드 팀에서 어떻게 협업하고 있는지 소개해드리려 해요. 빠르게 피드백 받을 수 있는 환경을 만들고, 사람이 개입해야하는 지점을 최소화 하고, 그리고 사람이 개입해야 하는 부분은 쉽게 일을 끝낼 수 있는 환경을 구성하려 노력했어요. 깊고 세세하게 설명하기 보다, 전체적인 흐름을 알 수 있게 작성했어요. 단계마다 깊은 내용은 추후 기회가 된다면 다뤄보도록 할게요.

목차

  • 하나의 저장소로 여러 서비스 관리하기
  • 항상 배포할 수 있도록 브랜치 전략 수립하기
  • 코드를 공유하기 전에 빠르게 피드백 받을 수 있는 환경 조성하기
  • 이슈 라벨링 (변화 범위 추적, 버저닝 단위 표시, 필요한 과정 표시)
  • 동작하는 소프트웨어를 통해 팀원과 논의하기
  • 변화를 항상 스테이징에 배포하기
  • 팀원 누구나 쉽게 배포할 수 있도록 파이프라인 구성하기
  • 맺음말

하나의 저장소로 여러 서비스 관리하기

monorepo 구성으로 여러 서비스를 하나의 GitHub 저장소에서 관리하고 있어요. npm 패키지나 여러 도구가 있지만, 별도의 설정이 필요하고 팀원 학습 비용이 발생해서 선택하지 않았어요. @으로 프로젝트 root를 참조할 수 있기 때문에 공유된 코드를 사용하기 위해 import 구문 외에 별도의 패키징 과정은 필요 없도록 구성했어요.

// 프로젝트 root 하위의 libs 폴더에서 something 참조
import { something } from '@/libs';

항상 배포할 수 있도록 브랜치 전략 수립하기

GitHub flow를 기반으로 운영하고 있어요. 브랜치 전략은 변화 단위와 관련되어 있어서 호흡이 짧고 작은 변화를 유도하기 위해 기본 브랜치를 항상 배포 가능한 상태로 유지하고 있어요. 설계와 구현, 테스트 코드 작성, 코드 리뷰, QA가 하나의 풀 리퀘스트에서 다 같이 이뤄지고 있어요.

기본 브랜치에 일어난 모든 변화는 이슈와 풀 리퀘스트에 담겨 있는 맥락을 쉽게 추적할 수 있게 Squash merge를 이용해 풀 리퀘스트 번호를 남기면서 병합하고 있어요. 하나의 풀 리퀘스트로 처리하기에 너무 큰 변화라면 기본 브랜치 기준으로 feature/* 브랜치를 생성해서 작은 변화를 feature 브랜치에 병합하고 최종 QA가 통과될 때 feature 브랜치를 기본 브랜치로 Rebase merge 하고 있어요. 그래야 작게 나눈 변화를 기본 브랜치에서 번호로 쉽게 추적할 수 있기 때문이에요.

브랜치를 만들어서 작업하는 도중 기본 브랜치에 반영된 변화를 가져올 때, 풀 리퀘스트를 제출하지 않았다면 리베이스를 권장하고 있어요. 이미 제출했다면 원활한 협업과 리뷰 맥락 유지를 위해 merge 커밋을 만들어서 푸쉬하도록 권장하고 있어요. Merge 커밋은 Squash merge 될 때 보조 메세지로 포함되지 않기에 이렇게 선택하게 되었어요.

코드를 공유하기 전에 빠르게 피드백 받을 수 있는 환경 조성하기

팀 컨벤션, 린트 규칙, 포맷팅, 테스트 코드 수정 누락 등은 중요하지만 코드 리뷰 과정에서는 더 중요한 부분에 집중하고자 huskylint-staged를 통해 Git 커밋할 때마다 아래 과정을 실행해주고 있어요.

  • TypeScript 타입 일치 확인 : tsc -p tsconfig.json —-noEmit
  • 린트 규칙 확인 : eslint —-fix (eslint-config-airbnb v18.0.1 기반으로 Airbnb 규칙 사용 중)
  • 코드 포맷팅 통일 : prettier —-write
  • 변경된 파일 관련 테스트 실행 후 실패한다면 바로 테스트 종료 : jest --bail --findRelatedTests

푸쉬하기 전에 커밋 훅에서 많은 부분을 미리 확인하기 때문에 다른 팀원이 리뷰할 때 타입 불일치, 정적 분석, 포맷팅, 테스트 실패 여부에 대해서는 신경을 쓰지 않아도 되며 CI도 깨뜨릴 확률이 낮아져요.

이슈 라벨링

여러 서비스를 하나의 저장소에서 관리하고 있고 공유하는 코드가 있다 보니 제출된 풀 리퀘스트의 변화가 어디까지 영향을 주게 될지 판단하기 어려웠어요. 또한 각 서비스의 버저닝 체계는 Semantic versioning을 따르고 있는데 다른 팀원이 일으킨 변화가 패치 버전을 올려야 하는지 배포하는 팀원이 쉽게 알기 어려웠어요. 이를 포함해 여러 문제를 어떻게 해결해나가고 있는지 설명해 드리려해요.

변화 범위 추적하기

여러 서비스를 하나의 저장소에서 관리할 때 풀 리퀘스트가 어떤 범위까지 영향을 줄 수 있는지 알기 쉽도록 scope: *처럼 라벨링을 해주고 있어요. 예를 들어, scope: app이라면 앱 서비스에 변화가 일어났다는 걸 의미해요. GitHub Actions의 labeler를 이용해 코드가 변화한 path를 이용해 폴더별로 알맞은 라벨이 자동으로 붙도록 설정해줬어요.

버저닝 단위 표시하기

배포할 때 어떤 버전을 변화시켜야 할지 쉽게 알 수 있도록 풀 리퀘스트를 제출한 팀원이 changes: patch 같은 라벨을 붙여주고 있어요. 앞으로는 conventional commit을 이용해 커밋 메시지에서 변경 범위와 어떤 버전 변화와 관련되었는지 쉽게 알 수 있도록 개선하려 해요.

필요한 과정 표시하기

풀 리퀘스트에 어떤 과정이 필요한지 알 수 있게 needs: * 라벨을 활용하고 있어요. 예를 들어, needs: QA가 붙어 있다면 아직 QA가 필요한 상황을 의미해요. needs: *가 붙어있다면 GitHub Status를 통과되지 않은 상태로 변경하도록 봇을 활용하고 있기에 풀 리퀘스트 병합이 허용되지 않아요.

동작하는 소프트웨어를 통해 팀원과 논의하기

가설을 수립하고 증명하는 과정에서 배운 교훈을 다시 제품에 반영하려 할 때, 가장 좋은 의사소통 방식은 작은 변화를 만들고 동작하는 소프트웨어를 기준으로 논의하는 방향이라 생각해요. 이를 위해 풀 리퀘스트를 제출했을 때 변화한 코드를 기반으로 실행 가능한 링크가 코멘트로 생성되도록 설정해뒀어요.

Storybook을 이용해서 React 컴포넌트를 구현하고 있고 각 서비스별로 연결된 스토리북이 있어요. 현재 모두싸인 제품을 위한 디자인 시스템을 구현하고 있는데 이러한 공통 UI 컴포넌트 스토리북에 쉽게 접근할 수 있도록 공개된 URL로 배포해뒀어요. 또한, 각 서비스를 개발할 때 스토리북 개발 서버를 한 번만 실행하면 서비스 관련 컴포넌트와 디자인 시스템의 컴포넌트를 같이 볼 수 있도록 설정해뒀어요. (Storybook에서 Composition을 지원하지만 스토리북 개발 서버를 한 개만 실행하고 싶어서 조금 다르게 설정했어요.)

풀 리퀘스트 별로 실행 가능한 링크를 만들 때 Netlify는 여러 서비스를 등록해서 운영하기에는 번거롭기도 하고 한국 CDN이 없어서 느리기도 해서 Vercel로 옮기려고 계획하고 있어요. (Vercel’s Monorepo)

변화를 항상 스테이징에 배포하기

기본 브랜치로 풀 리퀘스트가 반영됐을 때 내부 팀원이 접근할 수 있게 고정된 URL로 자동으로 배포되게 설정해뒀어요. 유닛 및 통합 테스트, 각 서비스별 빌드, 정적 분석이 통과돼야 스테이징 서비스로 배포할 수 있어요. 하나라도 실패한다면 아래처럼 프론트엔드 팀 채널에만 실패 알림이 뜨도록 했어요. 관련 팀원은 놀라서 달려오게 되겠죠?

항상 배포 가능한 상태를 만들어주기 위해 E2E 테스트 코드를 Cypress로 작성해오다가 작성 및 유지 비용이 생각보다 많이 들어서 코드 없이 테스트 시나리오를 구성할 수 있는 Datadog의 Synthetic Monitoring으로 옮길 계획을 가지고 있어요. (Datadog을 백엔드 팀에서 적극적으로 사용하고 있고 APM과 묶어서 한 번에 볼 수 있기에 선택했어요. 다른 선택지로는 Testim이 있었답니다. 이에 대한 내용은 인사이트가 쌓일 때 공유해볼게요.)

(프로덕션에서 이메일 회원가입 테스트 시나리오가 실패해서 놀란 가슴 쓸어내리고 있는 프론트엔드 엔지니어 1…)

팀원 누구나 쉽게 배포할 수 있도록 파이프라인 구성하기

하나의 저장소에서 여러 버전 형태를 운영하고 있어요. Semantic Versioning을 따르지만 원하는 서비스의 배포 파이프라인만 실행되도록 서비스 이름을 접두어로 붙여주고 있어요. 예를 들어, 앱 서비스의 v1.3.2를 배포하고 싶다면 app-v1.3.2로 태그 버전을 지정하고 릴리즈 노트를 발행한다면 앱 서비스로만 배포가 됩니다. 슬랙 #배포 채널에 배포를 담당한 팀원이 어떤 서비스에 변화가 있을 예정인지 다른 팀원도 쉽게 알 수 있게 공유한다면 릴리즈 노트를 발행하는 것 외에 배포를 위해 따로 해줘야 할 건 없어요. 슬랙 #배포 채널에 자동으로 알림이 오기를 기다리면 됩니다.

서비스마다 릴리즈 노트를 작성할 때 어떤 변화 사항이 해당 서비스에 영향을 줄지 판단할 때 이슈 라벨에 의존하고 있어요. 크게 거슬리는 작업은 아니지만 자동화되지 않은 부분이라 Release Drafter 이용해서 기본 브랜치에 풀 리퀘스트가 병합될 때마다 각 서비스별로 draft가 자동 생성되게 하려 했는데, 하나의 저장소에서 여러 태그 형태를 지원하지 않아서 다른 방법을 찾고 있어요.

맺음말

별도의 협업 가이드를 운영하는 코드 리뷰나 이슈 작성을 통한 요구사항 분석, 풀 리퀘스트 작성 방법까지 모두 담는다면 길어질 거 같아서 포함하지는 않았어요. 모두싸인 프론트엔드 팀에서 매일 겪게 되는 워크플로우를 간략하게 설명해 드렸는데 전체적으로 어떤 방식으로 흘러가는지 이해하셨길 바래요.

제품을 같이 만들어갈 프론트엔드 엔지니어를 채용하고 있어요. 관심있으시다면 적극 지원 부탁드려요!

모두싸인 채용공고 보러가기

--

--