Config-driven 아키텍처로 React 프로젝트 파편화 막기
호텔 예약 시스템을 개발하고 운영하다 보면, 새로운 호텔을 오픈할 때마다 각기 다른 비즈니스 요구사항을 마주하게 됩니다. 어떤 호텔은 비회원 예약을 허용해야 하고, 어떤 곳은 골프 복합 패키지를 추가해야 하며, 어떤 곳은 특정 결제 수단만 사용해야 합니다.
초기에는 기존 프로젝트를 복제하여 요구사항에 맞춰 코드를 수정하는 방식을 사용했습니다. 하지만 이런 방식은 결국 '프론트엔드 파편화' 와 '유지보수 지옥' 을 불러왔습니다. 공통 코드 베이스에는 끝을 알 수 없는 if-else 분기문이 쌓여갔죠. 이 기술 부채를 해결하기 위해 제가 선택한 방식은 Config-driven Architecture(설정 주도 아키텍처) 로의 전환이었습니다.
The Problem: 하드코딩된 조건문의 한계
코드를 복제해서 쓰는 방식은 당장의 개발 속도는 빠를지 몰라도, 장기적으로는 치명적인 단점을 가집니다.
- 버그 수정의 고통: 공통 로직에 버그가 발생하면 복제된 N개의 프로젝트 코드를 모두 열어서 수정하고 배포해야 합니다.
- 사이드 이펙트: A 호텔을 위한 조건문을 공통 컴포넌트에 추가했다가, B 호텔의 UI가 깨지는 일이 발생합니다.
- 코드 가독성 저하: 하나의 컴포넌트 안에 비즈니스 로직과 뷰가 강하게 결합되어 코드가 비대해집니다.
The Solution: 코드가 아닌 설정으로 제어하라
이 문제를 해결하기 위해, 코드를 수정하여 기능을 추가하는 방식에서 '관리자 페이지에서 기능을 ON/OFF 할 수 있는 구조' 로 아키텍처를 완전히 뒤집었습니다.
1. Initial Load 블로킹과 Config 주입 React 앱이 처음 로드될 때, 클라이언트는 서버 API를 호출하여 해당 프로젝트에 할당된 설정값을 한 번에 싹 긁어옵니다. 이때 데이터 무결성을 위해 Config 데이터를 완벽히 받아오기 전까지 앱의 메인 렌더링을 일시적으로 블로킹합니다. 자칫 빈 화면이 노출되어 UX를 해치는 것을 막기 위해, 브랜드에 맞는 로딩 스피너를 배치하여 자연스러운 진입을 유도했습니다.
2. Feature Flag 기반의 렌더링 최상단 Context에 주입된 Config 데이터를 바탕으로, 하위 컴포넌트들은 자신이 렌더링되어야 할지 말아야 할지를 스스로 결정합니다.
// 예시: Config 기반 조건부 렌더링
const { features } = useConfig();
return (
<PaymentContainer>
{features.ALLOW_GUEST_BOOKING && <GuestCheckoutButton />}
{features.USE_TOSS_PAYMENTS ? <TossWidget /> : <NicePayWidget />}
</PaymentContainer>
);
The Result & Retrospective
이 아키텍처를 도입한 후, 신규 호텔을 온보딩할 때 프론트엔드 코드를 단 한 줄도 수정할 필요가 없어졌습니다. 관리자 페이지에서 버튼 몇 개를 토글하는 것만으로 새로운 프로젝트 환경이 구성되었습니다.
"플랫폼은 코드의 공유가 아니라 책임의 분리다." 이 프로젝트를 통해 복제 기반의 시스템이 진정한 플랫폼으로 진화하려면, 비즈니스 분기 로직을 개별 컴포넌트가 아닌 상위 Config 레벨에서 중앙 통제해야 한다는 뼈저린 교훈을 얻었습니다. 향후 SSR(Next.js)을 도입하게 된다면, 이 Config 데이터를 서버 렌더링 단계에서 미리 주입하여 초기 로딩 속도까지 극한으로 끌어올리고 싶습니다.