TBD(Trunk Based Development)

2023. 1. 30. 13:28개발/프론트엔드 기술 세미나

개요

트렁크 기반 개발 (Trunk Based Development) 이란?

  • 참고: https://trunkbaseddevelopment.com/
  • 일반적인 트렁크 기반 개발 방법은 Trunk 라고 불리는 브렌치( 또는 Master 브렌치) 에 각각의 개발자들이 작은 변동 사항의 커밋들을 직접 커밋하여 개발하는 방법을 말합니다.
  • 즉, 개발자들은 Trunk (Master) 브렌치에서 협업하고, 긴 작업 단위의 브렌치 생성을 피하며, 이로인해 협업간에 생기는 충돌을 최소화 하고 빌드가 실패되는 상태를 막게 됩니다.
  • 이로인해 Trunk (Master) 브렌치는 항상 배포 가능한 상태가 유지 됩니다.

 

소규모 개발 팀을 위한 TBD

좀 더 큰 규모의 개발 팀을 위한 TBD

 

개발 규칙

  • 각 개발자들이 Trunk (Master) 브렌치에서 작은 단위의 개발을 진행 하고 바로 푸시 합니다.
  • 혹은, 짧은 수명(한 명의 개발자가 최대 2~3일 정도의 개발을 하는 분량)을 가지는 브렌치를 만들어 작업을 진행합니다.
    • 해당 브렌치는 Trunk (Master) 브렌치에 merge 하기 전에 PR을 통한 코드리뷰와 자동화된 빌드를 거칩니다.
    • 보다 장기간 지속되어야 하는 기능 개발의 경우 Feature FlagBranch by Abstraction 을 사용해 작업을 나눕니다.

 

배포 규칙

  • Trunk (Master) 브렌치에서 바로 배포를 진행합니다.
  • 혹은, Trunk (Master) 브렌치에서 배포가 필요한 시점에 배포 브렌치를 생성후 배포를 진행합니다.
    • 배포 브렌치에서 작업한 결과는 Trunk(Master) 브렌치로 머지하지 않아야 합니다.
    • 배포 브렌치에서 추가 작업이 필요할 경우 Trunk(Master)에서 작업을 수행한 후 해당 커밋을 체리픽으로 가져와야 합니다.

트렁크 기반 개발 방법의 장점

  • 소규모 단위의 커밋과 병합을 통해 머지의 복잡성이 줄어들어, 대규모의 병합을 할 때 발생하는 충돌 가능성을 줄일 수 있습니다.
  • 작은 단위로 계속해서 Trunk(Master) 브렌치가 업데이트 되므로, 최대한 많은 팀원들이 최신의 코드를 유지 할 수 있습니다.
  • 페어 프로그래밍과 빠른 피드백을 통해 개인 스타일보다는 팀 단위의 코딩 스타일로 작성 할 수 있으며, 보다 좋은 품질의 코딩이 가능 합니다.
  • 병합 충돌이 발생할 가능성이 매우 적어지기 때문에 대규모의 코드 리팩토링을 보다 쉽게 처리가 가능 합니다.

고전적인 기능 기반 개발 방법의 단점 해소

 

트렁크 기반 개발을 위한 조건

  • 하위 브렌치 없이 바로 Trunk 브렌치에 merge가 되기 때문에, merge가 되기전 해당 변동 사항이 다른 개발자들과 공유가 되고, 코드 에러나 코드 스타일들이 모두 검수된 뒤에, 문제가 없다고 판단 될 경우에 반영이 되어야 합니다.

 

자동화 되고 신뢰할 수 있는 빌드 시스템

  • 작성한 코드가 항상 릴리즈 되어도 문제 없는 상태임을 확신 할 수 있어야 하고, 이를 위해서 충분한 자동 테스트가 실행이 되어야 하며, 이러한 작업에 대해서 로컬에서 실시간 빌드를 통해 오류가 없는지를 확인하고 코드가 반영이 될 수 있도록 해야 합니다.

 

빠른 빌드 시스템

  • 위에서 언급된것 처럼 작은 기능 단위의 커밋이 계속 발생하고 짧은 수명의 브렌치가 계속 병합되므로 빌드 및 테스트 프로세스는 몇 분내에 실행이 완료 되어야 합니다.

 

실시간 코드 리뷰 및 페어 프로그래밍

  • 페어 프로그래밍을 하게 되면 팀원들이 같은 코드를 함께 작업 하기 때문에 자연스럽게 코드에 대한 컨텍스트 공유가 되고, 이후 PR 요청을 포함한 코드 리뷰 절차가 없어지게 됩니다. (작업을 더 빨리 완료할 수도 있으며, 코딩 스타일 통일 및 지식 공유등의 부가적인 이점이 있습니다.) 꼭 페어 프로그래밍이 아니여도 작업 사항에 대해 바로바로 팀원들에게 리뷰 요청을 하고, 다른 팀원들로부터 들어온 리뷰에 대한 검토를 가장 높은 우선순위로 처리 해야 합니다.

 

소규모 배치 개발

  • 개별적으로 몇 일 단위가 아닌 몇 시간 단위의 단기간에 완료할 수 있는 작은 단위로 쪼개어 작업을 진행 해야합니다. 이렇게 하면 프로덕션 릴리즈를 더 자주 진행 할 수 있으며, 각각의 변경 사항에 대한 피드백을 받는데 걸리는 시간이 단축 되며, 이로 인해 문제를 보다 쉽게 분류 하고 조치할 수 있습니다.

 

Branch by Abstraction 또는 Feature Flags 사용

  • Branch by Abstraction
    • 추상화된 브렌치 (Branch by Abstraction) 테크닉은 어플리케이션의 큰 변화를 만들때 주 흐름을 점진적으로 반영하는 패턴입니다.
    • 대체될 컴포넌트 (교체가 될 코드)와 이 컴포넌트를 의존하고 있는 코드들 사이에 추상화 레이어를 추가하고, 새로 도입할 컴포넌트는 아직 이코드들이 의존 하지 않도록 하여 커밋합니다.
    • 순차적으로 코드들이 새로운 컴포넌트에 의존하도록 변경하고, 이러한 작업이 완료 되면 기존 컴포넌트는 제거 합니다.
    • 참고

  • Feature Flags
    • Feature Flags (기능 플래그)는 응용 프로그램이나 서비스의 특정 기능을 켜고 끄는 방법입니다.
      • (특정 ui 요소를 표시 / 비표시 전환 하거나, 특정 로직을 on / off 하는등)
      • Feature Flags 를 사용 할 경우의 이점
        • 기능 출시 관리가 가능합니다.
          • Dev 에서는 기능을 활성화 하고 Staging 이나 Prod 에서는 기능을 비활성화 처리가 가능합니다.
          • 여러 변경 사항을 적용하고 배포할 때 이를 이용해 배포 복잡도를 줄일수 있습니다. 각각의 기능 및 각각의 서비스 마다 플래그를 도입하여 끄고 배포 한뒤, 이후 기능 플래그를 하나씩 켜짐 상태로 전환하여 모니터링이 가능 합니다.
        • 점진적 출시가 가능합니다.
          • 배포한 기능을 바로 사용자에게 보여주지 않고 지정한 인원만 접근하게 하여 운영 환경에서 테스트를 할 수 있습니다.
          • 기능에 문제가 생길경우 바로 해당 기능을 비활성화 처리 할 수 있습니다.
        • 시스템 안정성 관리가 가능 합니다.
          • 코드 배포 없이 시스템의 기능을 켜고 끄면서 시스템의 안정성을 유지 할 수 있습니다.
            • 예) 트래픽이 많아지는 경우 서버안정성을 위해 트래픽을 차단
      • 참고 링크

 

주의점

  • 코드 리뷰나 페어 프로그래밍등의 코드 검수 없이, 기능적 에러나 문제가 없는지 확인되지 않은채 Trunk에 바로 커밋을 반영 해버리는것은 TBD가 아닙니다.
  • 그 외 고려해야할 점은 아래와 같습니다.


코드 검토를 최우선으로

  • 하나의 코드가 검토되고 있는 동안 새로운 작업은 가급적 시작하면 안됩니다. 코드 병합이 지연 될수록 문제가 발생할 가능성이 커지므로, 서로의 코드를 우선적으로 검토하는 팀 합의가 필요합니다.


코드 검토 과정이 복잡하지 않도록

  • 코드 검토 과정이 복잡하면 복잡할수록 개발자는 그 복잡함을 피하기 위해 작은 10개 단위의 일을 10번의 코드 검토를 요청하지 않고 묶어서 크게 1개로 만들어 코드 검토를 요청하게 됩니다. 이는 즉, 코드 검토를 미루게 되는 현상이 발생합니다.


테스트 자동화

  • Trunk 브렌치는 항상 안정적인 상태를 유지해야 하므로, 코드 푸시는 언제나 테스트가 병행 되어야 하고, 이를 통해 푸시된 코드가 기본적인 안정성 검증을 통과하였음을 보장 할 수록 좋습니다.


결론

  • 어떻게 하면 빠르고 편한 코드 리뷰를 받고 & 해줄수 있는지 고려하고, 이를 위해 PR을 잘게 쪼개고, 작업한 사항에 대해 최대한 문제없이 빠르게 반영 할 수 있는지도 고려해야, 빠르고 효율적인 TBD 전략을 가져갈 수 있다. -> 간단하면서도 어렵다.


구현 내용

Jaje-frontend 에서 추상화 레이어 및 피쳐플래그를 구현한 내용

사전 작업

  • 서로 다른 환경에서 각각의 패키지 버전을 환경 변수로 만들어줌.
    • pacakage version을 읽어 환경 변수로 추가해주는 스크립트 명령을 실행
// .../package-version.js
async function getPackageVersion() {
  let version;
  const res = exec(
    "$(yarn version | grep version | awk -F\":\" '{ print $2 }' | sed '/^$/d' | tr -d ' ')",
    (error, stdout, stderr) => {
      version = stdout;
    },
  );
  await once(res, "close");
  return version;
}

function makeCommand(version) {
  return `NEXT_PUBLIC_PACKAGE_VERSION=${version} && VUE_APP_PACKAGE_VERSION=${version} && REACT_APP_PACKAGE_VERSION=${version}`;
}

(async function () {
  const res = await getPackageVersion();

  return makeCommand(res);
})();


// .../package.json
"bin": {
  "package-version": "package-version.js"
},
  • 각각 앱의 빌드 명령어에서 위에서 작성한 package version을 실행 시켜줌
// ex) next+react 환경의 packages.json

"build": "yarn package-version && next build",


플래그 구현

  • 패키지 버전을 받아 원하는 버전 이상일때 특정로직이 동작되도록 하는 공통 유틸 함수 구성.
    • 로직이 on 되는 시점 (원하는 시점)의 버전을 받아 현재 실제 버전과 비교하는 함수
/*-------------------------- 기본 기능 ------------------------------*/

// 현재 버전과 플래그를 켜줄 버전을 넘겨받음.
const currentVersion = PACKAGE_VERSION.split(".");
const compareVersion = version.split(".");


// 두 개의 버전을 major, minor, Patch 단위로 비교하여 비교할 버전 이상인지 체크.
let versionPass: boolean;
versionPass = currentVersion[0] < compareVersion[0] ? false
    : currentVersion[1] < compareVersion[1] ? false
    : currentVersion[2] >= compareVersion[2];


/*------------------------- 추가 옵션 1 -----------------------------*/


// 만약 플래그는 켜지지 않았지만, Prod 환경이 아닌 그 아래 환경에서는 동작 시키고자 할 때
if (!versionPass && accessDev) {
  if (accessDev === QA_FLAG_DEV) {
    versionPass = DEV_ENV === QA_FLAG_DEV;
  } else if (accessDev === QA_FLAG_STAGING) {
    versionPass = DEV_ENV === QA_FLAG_DEV || DEV_ENV === QA_FLAG_STAGING;
  } else {
    versionPass = false;
  }
	return versionPass && checkFeatureFlags(featureFlags);
} 

// Dev 는 Dev 환경 에서만,  Staging 은 Staging 과 Dev 환경에서 동작 하도록 처리.


/*------------------------- 추가 옵션 2 -----------------------------*/


type FeatureFlags = { [key: string]: () => boolean };

/**
 * 피쳐 플래그 체크
 */
const checkFeatureFlags = (features?: FeatureFlags) => {
  if (features) {
    return Object.keys(features).every(key => features[key]());
  }
  return true;
};

// boolean 반환값을 갖는 추가 피쳐 플래그들을 체크
  • 위에서 만든 함수만으로 사용이 가능 하지만, 버저닝이 계속 됨에 따라 if-else 문의 반복으로 코드가 길어질수 있으므로 이를 한번 더 추상화 하며 감쌈.
checkCondition([
  [checkVersion('2.0.0', QA_FLAG_DEV), <DevV3/>],
  [checkVersion('1.0.2'), <DevV2/>],
  [checkVersion('1.0.1'), <Dev/>],
  [<Prod/>]
])


플래그 사용

  • 병해충 진단 앱에서의 사용 예제중 하나. 추상화 레이어를 두고 플래그 전환이 이루어 집니다.

 

'개발 > 프론트엔드 기술 세미나' 카테고리의 다른 글

Sentry  (0) 2023.02.02
Storybook  (0) 2023.02.01