- 원본 개발
- 약 2개월 반
- 신규 도메인 이식
- 5일 (2026-03-09 ~ 03-13)
- 이식 커밋
- 6개
- 그대로 재사용한 모듈
- 9개 파일
- 재사용률
- 약 65~70%
배경
피클볼 국제 레이팅 시스템인 DUPR 연동을 OAuth 계정 연결부터 경기 결과 제출까지 처음부터 끝까지 만들었습니다. 약 2개월 반이 걸린 작업이었고, 두 달 뒤 신규 서비스인 오픈플레이에 같은 기능이 필요해졌을 때 5일 만에 붙었습니다.
처음에 어디에 둘지
빠르게 붙은 이유는 이식할 때 잘해서가 아니라 처음 만들 때 어디에 둘지를 정해뒀기 때문입니다.
자연스러운 배치는 이벤트 페이지 폴더 안이었지만, 그러면 나중에 꺼내는 리팩토링부터 해야 하고 그게 이식 비용의 대부분이 됩니다. 처음부터 DUPR 로직을 특정 도메인이 아닌 공용 위치(commons/, api/hooks/, hooks/api/)에 두고 화면과 로직을 분리했습니다. 덕분에 오픈플레이에서는 리팩토링 없이 바로 import할 수 있었습니다.
흐름은 안에, 트리거는 밖에
연동 흐름은 소개 → OAuth iframe → 연동 완료 → 클럽 선택 → 클럽 연결 완료로 이어지고, 계정 종류에 따라 갈라집니다. 여섯 개의 다이얼로그를 오가는 이 흐름(단일 경로로는 최대 네 단계)을 한 컴포넌트에 캡슐화하되, 트리거 버튼의 모양과 위치는 쓰는 쪽이 정하도록 Render Props로 열었습니다. 흐름은 어디서 쓰든 같지만 트리거는 화면마다 다르기 때문입니다.
type DUPRConnectProps = {
connectButton: ({ onClick, onOpenClubSelect }) => ReactNode; // 밖에서 정함
onComplete?: (type: 'standard' | 'premium' | 'club') => void;
onClose?: () => void;
shouldCheckPremium?: boolean;
};
밖에서 받는 건 트리거 UI와 완료 콜백뿐이고, 다이얼로그 여섯 개의 열림 상태와 단계 전이, OAuth iframe, 계정 종류 분기까지 283줄이 전부 안에 있습니다. 이벤트, 오픈플레이, 설정 페이지 어디서든 connectButton과 onComplete만 넘기면 전체 흐름이 동작하고, onComplete가 넘겨주는 연결 종류로 후속 작업의 타입도 보장됩니다. 다이얼로그는 모두 동적 import라 연동을 쓰지 않는 화면에서는 0KB입니다.
도메인이 늘 때 손댈 곳 좁히기
- API 훅은 중앙에 두고 도메인별 함수만 추가합니다. 이벤트용
submitEventDuprMatches옆에 오픈플레이용 함수 두 개만 더했고, 새 파일은 만들지 않았습니다. - 쿼리 정의는 팩토리로 공유해 키만 바꿉니다. 같은 모양의 데이터는 queryKey와 파라미터만
event에서open-play로 갈라 재사용합니다. - 폼은 필요한 최소 타입만 요구하도록 제네릭으로 열었습니다. 특정 이벤트 타입에 묶이지 않아 여러 폼에서 수정 없이 쓰입니다.
- 번역 키도 공통 네임스페이스에 뒀습니다.
eventAdmin같은 특정 도메인이 아니라dupr네임스페이스라, 두 도메인이 같은 문구를 참조합니다.
빠뜨린 분기는 컴파일러가 알려 주게
결과 제출 다이얼로그는 type으로 제출 종류(이벤트 DUPR, 오픈플레이 DUPR, 랭크 매치)를 받고, 종류마다 다른 API의 데이터를 씁니다. 나중에 종류가 늘어날 것이라 default에서 남은 값을 never에 할당해 두었습니다.
switch (type) {
case 'duprSubmit':
return duprStatus?.eligibility;
case 'openPlayDuprSubmit':
return openPlayDuprStatus?.eligibility;
case 'rankMatchSubmit':
return rankMatchStatus;
default: {
const _exhaustiveCheck: never = type;
throw new Error(`Unhandled type: ${_exhaustiveCheck}`);
}
}
오픈플레이용 openPlayDuprSubmit을 추가하자 이 default에서 곧장 컴파일 에러가 났고, 데이터 분기를 빠뜨렸다는 걸 실행하기 전에 알 수 있었습니다. 원리는 블로그 글에 따로 정리했습니다.