웹 개발은 이게 맞아
이 회사에 와서 가장 좋은점이 뭐냐? 라고 누군가 묻는다면 정말 꼽기가 힘들 정도로 배울 점이 많은데, 그중에 하나만 꼽으라면 깃 브랜치 전략 문화이다.
닥터팔레트 프론트엔드 웹 서비스는 TBD 전략을 사용하고 있는데, 이게 아주 기가 막히다.
구글이 사용하는 TBD 전략이 뭔지, 그리고 닥팔팀에서 어떻게 사용하는지 정리해보려 한다.
본격적으로 TBD를 찬양하기 전에, 흔히 접해왔던, 그리고 필자가 실제로 전회사까지 써왔던 브랜치 전략들을 잠깐 짚고 넘어가 보자.
왜 TBD가 새로운 맛인지, 왜
Vincent Driessen이 제안한 이 전략은 개발자라면 한 번쯤 들어봤을 '국룰'이다.
master (혹은 main), develop, feature, release, hotfix 등 브랜치의 역할이 엄격하게 나뉘어 있다.
이 과정은 안정적인 버전 관리가 필요한 패키지 소프트웨어(앱, 게임 등)에는 적합할지 몰라도, 하루에도 수십 번씩 배포가 일어나는 웹 서비스 환경에서는 너무 무겁다.
기능 하나 배포하려면 feature → develop → release → master의 기나긴 여정을 거쳐야 한다.
Git Flow의 복잡함을 걷어내고, master 브랜치와 feature 브랜치만 남겼다.
PR(Pull Request)을 통해 코드 리뷰를 하고, 머지되면 즉시 배포한다. CI/CD 친화적이고 빠르다.
하지만 팀 규모가 커지면 이야기가 달라진다.
feature를 따서 작업하다 보면, 배포 시점에 충돌(Conflict) 해결하느라 시간을 다 보낸다.master는 변해있다.‘머지 스트레스 없이, 준비된 코드는 즉시 나간다’ 는 철학이 필요했다.
그게 바로 지금 소개할 Trunk Based Development (TBD) 다.

A portal on this practice
공식문서의 한줄 요약(One line summary)을 확인해보면 TBD의 철학은 명확하다.

Github Flow와 유사한듯 하지만, 훨씬 가볍고 빠르다.
TBD의 규칙을 정리해보면 다음과 같다.
간단 명료하지 않은가 ?
언제든 배포 가능한 하나의 trunk 브랜치를 가지고, 가능한 작은 작업(micro PR)로 trunk에 병합한다.
그러나 개발자로 살다보면 기획이 엎어지기도, 디자인이나 백엔드 일정이 예정보다 늦어지기도, 예상치 못한 문제에 피처를 오래 가져가는 일도 반드시 있는 법.
trunk based가 되는 메인 브랜치는 언제든 배포가 가능한 상태여야 한다.
그래서 TBD 전략을 사용하기 위해 반드시 필요한것이 세번째 규칙인 피처 플래그(Feature Flag) 다.

A portal on this practice
기능을 개발했을때 그 기능을 특정 인원에게만 접근할 수 있게하는 방법론을 뜻한다.
피처 플래그의 예시로 구글의 유튜브와 깃허브를 들어볼 수 있다.
여기서 다시한번 짚고가야 할 규칙은 위에 언급한 두번째 규칙이다.

A portal on this practice
복잡하고 큰 기능이어도 상관 없다. 그냥 구현한 코드를 정리해서 피처플래그를 꺼놓으면 된다.
그래서 피처플래그의 가장 중요한 특징은 ‘제품 배포 없이 플래그를 활성화/비활성화 할 수 있어야한다’ 라는 점이다.
그래서 우리는 LaunchDarkly 라는 유료 서비스를 사용해 피처플래그를 관리하고 있다.
루트에 LDProvider 를 감싼다.
<LDProvider>
<StrictMode>
<App />
</StrictMode>
</LDProvider>useFlags 로 LaunchDarkly에 등록한 플래그를 가져온다.
(보통 단순 boolean 값만으로 관리가 충분하다.)
const HooksDemo = () => {
// 키 값은 카멜케이스로 가져올 수 있다.
const { devTestFlag } = useFlags();
return (
<Wrapper>
<FlagDisplay>{devTestFlag ? <span>Flag on</span> : <span>Flag off</span>}</FlagDisplay>
</Wrapper>
);
};닥팔 웹 서비스는 현재 60여개의 서브패키지로 관리되고 있다.
모듈 페더레이션의 호스트를 다르게 설정해서 켰을때, 상위에 LDProvider 에 감싸지지 않아서 에러가 나는 문제를 해결하고, 언제나 원하는 호스트로 켜기 위해 WithLD 라는 HOC 를 만들어서 적용했다.
import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk';
export const WithLD = ({ children }: PropsWithChildren<unknown>) => {
const root = document.getElementById('app');
if (!root) {
throw new Error('root is not found');
}
(async () => {
const LDProvider = await asyncWithLDProvider({
clientSideID: process.env.LAUNCH_DARKLY_KEY ?? '',
context: {
// context values
},
options: {
streaming: true,
},
});
createRoot(root).render(
<LDProvider>
<StrictMode>{children}</StrictMode>
</LDProvider>,
);
})();
};
이렇게 만든 HOC는 bootstrap.tsx 에서 App을 감싸면 된다.
async function bootstrap() {
WithLD({ children: <App /> });
}
bootstrap();