[API Design] API Versioning: URL / Header / Date 비교
API versioning, API breaking change, Stripe API version, deprecation policy, semver API
1. URL 버전 (
정의
API Versioning 은 breaking change 를 클라이언트에 강요 없이 호환성을 유지하는 전략. 잘못 잡으면 모든 클라이언트 동시 마이그레이션 강요.
무엇이 Breaking Change?
| Breaking | Non-breaking |
|---|---|
| 필드 제거 / 이름 변경 | 새 필드 추가 (옵션) |
| 필드 타입 변경 | 새 endpoint 추가 |
| 필수 필드 추가 | 새 enum 값 추가 (옵션 일 때만) |
| Enum 값 제거 | 새 query param (옵션) |
| Endpoint 삭제 | 새 응답 헤더 |
| 응답 status code 변경 | rate limit 조정 |
| Auth 방식 변경 | 에러 메시지 개선 (코드 유지) |
4가지 버전 전략
flowchart TD
Q{버전 표시 방법}
Q --> URL["URL 버전<br/>/v1/users"]
Q --> Header["Accept 헤더<br/>application/vnd.api+json;version=2"]
Q --> Custom["커스텀 헤더<br/>X-API-Version: 2"]
Q --> Date["날짜 버전<br/>Stripe-Version: 2023-10-16"]
1. URL 버전 (/v1/...)
GET /v1/users/42
GET /v2/users/42
장점: 명시적, 브라우저 친화, 캐시 가능, debugging 쉬움. 단점: URL 영구 변경. 자원이 같은데 URL 다름.
가장 흔한 방식. 시작점으로 권장.
2. Accept 헤더
GET /users/42
Accept: application/vnd.example+json;version=2
장점: URL 유지.
단점: 디버깅 어려움 (curl 시 헤더 잊음), 캐싱 복잡.
3. 커스텀 헤더
GET /users/42
X-API-Version: 2
장점: 단순. 단점: 표준 아님.
4. 날짜 기반 (Stripe 방식)
GET /users/42
Stripe-Version: 2023-10-16
Stripe 는 모든 변경에 발표 날짜. 클라이언트는 고정 날짜로 pin. 새 버전으로 명시적 upgrade.
flowchart LR
A["2023-10-16<br/>(고객 1 pin)"] --> Impl
B[2024-04-10] --> Impl
C[2025-01-15] --> Impl
D["2026-06-25<br/>(최신)"] --> Impl
Impl["서버<br/>(다중 버전 동시 지원)"]
기업 사례
Google (URL 버전)
GET https://www.googleapis.com/calendar/v3/calendars/primary
GET https://youtube.googleapis.com/youtube/v3/videos
모든 Google API 가 /v{N}/ URL 버전 채택. 버전 N = 대규모 재설계 시점.
Stripe (날짜 버전)
GET /v1/charges
Stripe-Version: 2023-10-16
- 매 변경마다 날짜 버전 발행
- 기존 API key 는 발급 당시 최신 버전으로 고정
- 개발자가 명시적 upgrade 결정
- 같은
/v1/경로, 날짜로 세분화
GitHub (Accept 헤더 버전)
GET /repos/octocat/hello-world
Accept: application/vnd.github.v3+json
X-GitHub-Api-Version: 2022-11-28
- 날짜 기반 버전 (2022-11-28 형식)
X-GitHub-Api-Version커스텀 헤더로 전달
Twitter / X (URL 버전)
GET https://api.twitter.com/2/tweets
GET https://api.twitter.com/1.1/statuses/show.json
v1.1, v2 를 장기간 병행 지원 (마이그레이션 부담 최소화).
Deprecation Policy
gantt
title API v1 의 라이프사이클
dateFormat YYYY-MM-DD
section Active
v1 활성 + 지원 :a1, 2024-01-01, 2025-12-31
section Deprecated
Deprecation 경고 :crit, d1, 2025-06-01, 2026-06-01
section Sunset
Sunset 일시 차단 :s1, 2026-06-01, 2026-12-31
완전 종료 :milestone, m1, 2026-12-31, 0d
| 단계 | 의미 | 헤더 |
|---|---|---|
| Active | 정상 사용 | - |
| Deprecated | 새 사용 자제 | Sunset: <date>, Deprecation: <bool> |
| Sunset (graceful) | 사용 가능, 적극 경고 | 응답에 경고, 일부 endpoint 410 |
| Removed | 410 Gone | - |
IMPORTANT
Sunset 까지 최소 6-12개월 예고가 정통. 고객사가 단순 라이브러리만 따라잡으면 되도록.
Sunset 헤더 구현 (RFC 8594)
// Express 미들웨어 예시
function deprecationMiddleware(req: Request, res: Response, next: NextFunction) {
if (req.path.startsWith("/v1/")) {
// RFC 8594: Sunset 헤더
res.set("Sunset", "Sat, 31 Dec 2026 23:59:59 GMT");
// draft RFC: Deprecation 헤더
res.set("Deprecation", "Tue, 01 Jun 2025 00:00:00 GMT");
res.set("Link", '</v2/docs>; rel="successor-version"');
}
next();
}
클라이언트 측에서 Sunset 헤더를 감지해 알림:
const response = await fetch("/v1/users");
const sunset = response.headers.get("Sunset");
if (sunset) {
const sunsetDate = new Date(sunset);
console.warn(`[API] v1 가 ${sunsetDate.toLocaleDateString()} 에 종료됩니다. v2 로 마이그레이션 필요.`);
}
Breaking Change 감지 도구
| 도구 | 기능 |
|---|---|
| openapi-diff | 두 OpenAPI spec 의 breaking change 리포트 |
| oasdiff | CLI, PR check 에서 자동 실행 |
| Bump.sh | SaaS, spec 변경 시 changelog 자동 생성 |
| Optic | CI/CD 통합, traffic 기반 spec 추출 |
# oasdiff 로 breaking change 감지
oasdiff breaking openapi-v1.yaml openapi-v2.yaml
# 출력 예시:
# GET /users/{id} response property 'email' removed (breaking)
# POST /users request body property 'phone' required (breaking)
호환성 카테고리
flowchart TD
Forward["Forward Compatible<br/>옛 클라이언트 to 새 응답 OK"]
Backward["Backward Compatible<br/>새 클라이언트 to 옛 서버 OK"]
Both[양방향 호환]
Tip1["새 필드 추가 (옵션) to 양방향"]
Tip2["필드 제거 to 양방향 깨짐"]
Tip3["응답에 옵션 필드 to forward compatible"]
Forward --> Tip1
Forward --> Tip3
Backward --> Tip1
Both --> Tip2
클라이언트 라이브러리 정책
| 정책 | 효과 |
|---|---|
| Unknown field 무시 | forward compatible. 클라이언트 라이브러리의 기본 |
| Unknown field 에러 | 엄격하지만 호환성 깨짐 |
| Optional 만 처리 | 가장 안전 |
Protobuf, JSON 모두 unknown field 무시 가 기본.
Changelog 관리 패턴
## 2026-06-25
### Breaking Changes
- `GET /v1/users` 응답에서 `full_name` 필드 제거 (`first_name` + `last_name` 분리)
### New Features
- `GET /v2/users` 에 `first_name`, `last_name` 추가
- `POST /v2/users/bulk` 배치 생성 endpoint 추가
### Deprecations
- `GET /v1/users` 2026-12-31 에 종료 예정
Changelog 는 클라이언트가 마이그레이션 결정 시 참고하는 핵심 문서. 자동화 도구 (Bump.sh, Optic) 로 PR 단계에서 자동 생성 권장.
흔한 함정
WARNING
- 버전 없이 시작 = breaking change 강요. 처음부터 v1 박기.
POST응답에 enum 값 추가 = 클라이언트의 switch case 폭발. unknown case 처리 강제.- deprecation 없이 종료 = 고객 폭동. 최소 6개월 전 알림.
- 버전 수 폭증 = 5개 동시 지원 = 코드 분기 폭발. 최대 2-3개 동시 지원 권장.
- Sunset 헤더 없는 deprecation = 클라이언트가 종료 일정 파악 불가. RFC 8594 헤더 적극 활용.
관련 위키
이 글의 용어 (4개)
- [API Design] OpenAPI / Swagger: 스펙, 코드 생성, contract testingapi-design
- 정의 OpenAPI Specification (OAS) 은 REST API 를 기술하는 YAML/JSON 형식. 옛 이름 Swagger. 2017 부터 OpenAPI Initia…
- [API Design] REST API: 원칙, 자원 모델링, HATEOAS, 버전 관리api-design
- 정의 REST (Representational State Transfer) 는 Roy Fielding 의 2000년 박사논문에서 정립된 분산 시스템 아키텍처 스타일. HTTP 의…
- [DevOps] 무중단 배포: Blue-Green, Canary, Rolling, Expand-Contractdevops
- 정의 무중단 배포 (Zero-Downtime Deployment) 는 서비스 가동을 멈추지 않고 새 버전을 배포하는 일련의 전략. 핵심 도전 4가지: 1. 트래픽 전환: 어떻게 …
- [Pattern] Idempotency Keys: 중복 요청 안전 처리distributed-systems
- 정의 Idempotency = 같은 요청을 N번 보내도 결과가 1번과 동일. 분산 시스템 / 결제 / API 의 안전망. [!IMPORTANT] 네트워크는 항상 timeout /…
💬 댓글