#AI
#Claude
#Rest API
#Backend
Korean |
English
Written by Paul
February 27, 2026
지난 편에서 billets-server의 API 문제 8가지를 분석했다. 이번엔 그 목록을 들고 실제로 코드를 고쳤다.
Phase 1의 원칙은 하나였다. 클라이언트 계약을 바꾸지 않는다.
/v1 엔드포인트를 그대로 두고, 서버 내부에서 조용히 정리할 수 있는 것들만 손댄다. 클라이언트가 업데이트 없이도 기존처럼 동작해야 한다.세 가지 작업으로 나눴다.
- dev-only 라우트를 Admin 서버로 이전
- 중복 슬러그 라우트 제거
- HTTP 상태 코드 정정
1-1. dev-only 라우트 분리 — 프로덕션 서버에서 꺼내기
문제
billets-server의 /v1/feed 라우트 파일 안에 이런 코드가 있었다.피드 데이터를 수동으로 등록하는 Admin 전용 기능이다. 개발 환경에서만 열리도록 환경 변수로 가드를 걸어놨다. 처음엔 이게 합리적으로 느껴졌다. 어차피 개발자만 쓰는 거니까.
문제는 시간이 지나면서 드러났다.
- 라우트 파일 어딘가에
if (NODE_ENV === 'development')블록이 숨어있어서 가시성이 낮다
- 인증이 전혀 없다. dev 환경에서 URL만 알면 누구든 호출할 수 있다
- 무엇보다, 이건 Admin 작업이다. 일반 API 서버에 있으면 안 된다
AI와의 대화
AI에게 개선 방향을 물었을 때, 예상했던 "admin prefix를 만들자"가 아니라 이런 답이 왔다.
"이 기능들은wamuseum-server로 이전하는 게 가장 명확합니다. 동일한 DB에 접근하는 Admin 전용 GraphQL 서버이고, Staff 인증이 이미 완전히 구현되어 있습니다. billets-server에 admin prefix를 추가하는 것보다, 목적에 맞는 서버에 붙이는 게 낫습니다."
맞는 말이었다.
wamuseum-server는 이미 Staff 인증 (authorizeUser())이 구현되어 있고, 동일한 Prisma 스키마를 쓴다. 새 auth 인프라를 만들 필요가 없다.이전 작업
wamuseum-server에 GraphQL 타입 추가:
wamuseum-server에 Resolver 추가:
billets-server에서 dev-only 블록 제거:
AI가 예측하지 못한 난관: registerKopis
작업 중 AI가 한 가지 케이스를 별도로 표시했다.
"registerKopis는billets-server의concertService에 의존합니다. 외부 KOPIS API를 호출하고, 그 결과를 billets-server의 concert 등록 로직으로 처리합니다. 이걸 wamuseum-server로 옮기려면 해당 서비스 로직도 함께 이전해야 합니다."
처음엔 "그러면 나머지 3개만 이전하고 registerKopis는 나중에 처리하자"고 결정했다. AI도 동의했다.
그런데 실제로 코드를 파보니
concertService의 해당 로직이 생각보다 자급자족적이었다. Prisma 호출과 KOPIS API 호출만 있고, billets-server 특유의 Fastify 플러그인에는 의존하지 않았다. 결국 registerKopis도 함께 이전했다.AI의 초기 판단은 보수적이었다. 코드를 실제로 열어본 사람이 판단을 바꿨다.
1-2. 중복 슬러그 라우트 — 살아있는 레거시
문제
두 경로가 동일한 이벤트를 반환한다. 오래된 앱 버전에서
/event/detail을 쓰고, 최신 버전에서 /event/slug/:slug를 쓴다. 같은 핸들러를 두 곳에서 가리키고 있었다.제거 대신 301 Redirect
AI가 "레거시 라우트를 제거하자"고 제안했을 때, 한 가지 우려가 들었다.
앱스토어에서 앱 업데이트를 강제할 수 없다. 오래된 버전을 쓰는 사용자가 있을 수 있다. 그 버전의 앱이
/event/detail?slug=xxx를 호출하면 갑자기 404를 받게 된다.AI도 이 지점을 인정했다.
"모바일 앱의 경우 즉시 제거보다 301 Redirect가 안전합니다. 구형 클라이언트는 투명하게 새 경로로 리다이렉트되고, 서버 입장에서도 라우트 핸들러는 하나로 통합됩니다."
변경 전:
변경 후:
venue도 동일하게 처리했다.
결과
레거시 라우트는 살아있지만 이제 핸들러 코드가 없다. 트래픽은 항상
/slug/:slug로만 처리된다. 나중에 구형 앱 트래픽이 충분히 줄었다고 판단되면 301 라우트 자체를 제거하면 된다.1-3. HTTP 상태 코드 정정
이 작업은 가장 단순하면서 동시에 가장 조심스러웠다.
상태 코드를 바꾸는 건 기술적으로 비파괴적이지 않을 수도 있다. 클라이언트가
if (status === 200)으로 성공 여부를 판단하고 있다면, 201이나 204가 오면 실패로 처리할 수 있다.AI에게 이 리스크를 물었다.
"HTTP 표준에서 2xx는 모두 성공입니다. 올바르게 작성된 클라이언트는 200/201/204를 동일하게 성공으로 처리해야 합니다. 하지만 실제로는status === 200으로 하드코딩된 클라이언트가 있을 수 있습니다. 클라이언트 코드를 확인해보는 것이 좋습니다."
billets-app 코드를 확인했다. React Query와 axios를 쓰는데, axios는 기본적으로 2xx 전체를 성공으로 처리한다. 응답 데이터를 status가 아닌 body로 판단하고 있었다. 안전하다고 판단했다.
변경:
작은 변경이지만 의미가 있다. 201은 "리소스가 생성됐다"는 신호다. 204는 "성공했고 돌려줄 내용이 없다"는 신호다. API를 처음 보는 개발자가 상태 코드만으로 의도를 이해할 수 있어야 한다.
AI와의 작업을 돌아보며
Phase 1을 마치고 나서 느낀 점이 몇 가지 있다.
AI가 잘한 것: 코드베이스를 빠르게 파악하고, 변경 범위를 명확히 분류했다. 특히 "이건 비파괴적이다", "이건 클라이언트에 영향이 있다"는 판단을 구조적으로 잘 정리해줬다.
사람이 개입한 지점들:
registerKopis이전 가능성 판단 — AI는 복잡하다고 봤지만 실제 코드를 보니 가능했다
- 301 redirect 채택 — 모바일 앱 생태계의 강제 업데이트 불가 문제는 도메인 맥락이다
- 클라이언트 코드 확인 — 상태 코드 변경의 안전성은 billets-app 코드를 직접 열어봐야 알 수 있었다
AI는 "이 변경이 올바른가?"는 잘 판단한다. "이 변경이 지금 이 팀에게 안전한가?"는 사람이 판단해야 한다.
Phase 1 결과 요약
항목 | 변경 내용 | 상태 |
dev-only 라우트 4개 | wamuseum-server GraphQL Mutation으로 이전 | ✅ |
/event/detail 레거시 | 301 redirect → /event/slug/:slug | ✅ |
/venue/detail 레거시 | 301 redirect → /venue/slug/:slug | ✅ |
POST /subscribe/* | 200 → 201 | ✅ |
DELETE /subscribe/* | 200 → 204 | ✅ |
POST /survey/count | 200 → 201 | ✅ |
클라이언트는 아무것도 바꾸지 않아도 된다. 서버는 조금 더 명확해졌다.
다음 편 예고
다음은 본격적인 v2 설계다.
/v1을 유지하면서 /v2 prefix로 RESTful 구조를 새로 설계한다. URL 복수형 통일, 자원 계층 구조 재편, 이벤트 필터 쿼리 파라미터 표준화까지.3편: Phase 2 — v2 API 설계 에서 계속됩니다.
이 시리즈는 실제 프로덕션 코드베이스를 AI와 함께 리팩토링하는 과정을 기록합니다. 모든 결정에는 근거가 있고, 모든 근거에는 트레이드오프가 있습니다.