Files
2nd/10_Wiki/Topics/Coding/API_Versioning_Strategies.md
T
2026-05-09 21:08:02 +09:00

237 lines
5.5 KiB
Markdown

---
id: api-versioning-strategies
title: API Versioning — URL / Header / Date 전략
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [api, versioning, vibe-coding]
tech_stack: { language: "TS", applicable_to: ["Backend"] }
applied_in: []
aliases: [API versioning, semver, breaking change, deprecation, sunset header]
---
# API Versioning
> Breaking change = client 깨짐. **URL versioning (`/v2`) 가 가장 단순**. Date versioning (Stripe) 이 정밀. **Sunset header + 6+ 개월 deprecation**.
## 📖 핵심 개념
- Breaking: 필드 제거, type 변경, 동작 변경.
- Non-breaking: 필드 추가, optional 추가, enum 값 추가 (cautious).
- Deprecation: 사용 가능 but 제거 예정.
- Sunset: 이 날짜에 제거.
## 💻 코드 패턴
### URL versioning (가장 일반)
```
GET /v1/orders
GET /v2/orders
```
```ts
app.use('/v1', v1Router);
app.use('/v2', v2Router);
```
```
장점: 간단, 캐시 친화, 명확.
단점: URL 변경 = client 코드 변경.
```
### Header versioning
```
GET /orders
Accept: application/vnd.acme.v2+json
```
```ts
app.get('/orders', (req, res) => {
const version = parseAccept(req.headers.accept);
if (version >= 2) return v2Handler(req, res);
return v1Handler(req, res);
});
```
```
장점: URL 깔끔.
단점: Cache 어려움 (Vary), 디버깅 어려움.
```
### Query versioning
```
GET /orders?api_version=2
```
→ 임시 / experimental 만. 공식 X.
### Date versioning (Stripe)
```
GET /orders
Stripe-Version: 2024-11-20
```
```
장점: 매우 정밀, account 별 lock.
단점: 복잡, transformation chain 유지.
```
```ts
// 변환 chain
const transforms: Record<string, (data: any) => any> = {
'2024-11-20': (d) => ({ ...d, oldField: d.newField }), // 옛 client
'2025-01-15': (d) => d, // current
};
// 응답 시
const clientVer = req.headers['stripe-version'] ?? '2025-01-15';
let data = currentResponse;
for (const v of versionsAfter(clientVer)) {
data = transforms[v](data);
}
return data;
```
### Backwards-compatible 변경
```ts
// ✅ 새 필드 추가
{ id, status, createdAt, refundedAt: '2026-05-09' } // refundedAt 새 — old client 무시
// ✅ Enum 값 추가 (cautious)
{ status: 'open' | 'paid' | 'shipped' | 'returned' } // 'returned' 새
// ❌ Type 변경
{ amount: 100 } { amount: '100.00' } // breaking
// ❌ 필드 제거
{ id, status } { id } // breaking
// ❌ 의미 변경
status: 'paid' (이전: 결제+ OK) status: 'paid' (이전: 결제 OK )
```
### Deprecation header
```
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.acme.com/v2-migration>; rel="deprecation"
```
```ts
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
res.set('Link', '<https://docs.acme.com/v2>; rel="deprecation"');
```
→ Client 가 검출 + 알림.
### Tracking version usage
```ts
app.use((req, res, next) => {
const version = req.path.startsWith('/v1') ? 'v1' : 'v2';
metrics.increment('api.calls', { version, endpoint: req.route?.path });
next();
});
```
→ 어떤 client 가 옛 version 인지 → email.
### 점진 migration
```
Phase 1 (출시): v2 출시, v1 그대로
Phase 2 (3개월): v2 권장 — docs 업데이트
Phase 3 (6개월): v1 deprecation header
Phase 4 (12개월): v1 sunset 발표 (3개월 후)
Phase 5 (15개월): v1 종료, redirect to v2 또는 410 Gone
```
### Field deprecation (소규모)
```ts
// 응답 안 deprecation 메타
{
id: '...',
status: 'paid',
oldField: 'foo',
_deprecated: ['oldField'] // 메타
}
```
또는 docs / changelog 만.
### Mobile app (가장 어려움)
```
OS app store review = 며칠
Force update 가능? 사용자 화남.
→ Backwards-compatible long term + min app version
```
```ts
// 사용자가 너무 옛 app
if (req.headers['x-app-version'] < '2.0') {
return res.status(426).json({ // Upgrade Required
type: '...',
title: 'Please update the app',
minVersion: '2.0',
});
}
```
### Internal API — 다름
- 같은 organization = breaking 가능 + monorepo 동시 update.
- 다른 팀 = public API 처럼.
### Federation / GraphQL
```graphql
type Order {
id: ID!
status: OrderStatus!
oldField: String @deprecated(reason: "Use newField")
newField: String
}
```
→ Breaking 없이 점진.
### Schema breaking 검출 (CI)
```bash
# OpenAPI
oasdiff diff old.yaml new.yaml --breaking-only
# GraphQL
graphql-inspector diff schema.old.graphql schema.new.graphql
```
→ PR check.
## 🤔 의사결정 기준
| 상황 | 전략 |
|---|---|
| Public REST | URL versioning (/v1, /v2) |
| Stripe-like 정밀 | Date versioning |
| Internal | Header / monorepo lockstep |
| GraphQL | @deprecated + 새 field |
| Mobile-heavy | Backwards-compatible 길게 |
| 작은 / fast iteration | Single version + 동시 release |
## ❌ 안티패턴
- **Breaking 발표 없이**: client 깨짐. communication 필수.
- **Deprecation 없이 즉시 제거**: 큰 incident.
- **Version 너무 많음 (v1, v2, v3, v4)**: 유지 부담. 최대 2.
- **Old version 영원 유지**: 코드 부담. sunset.
- **Header versioning + cache 안 함**: stale 또는 noise.
- **Mobile app 강제 update**: 사용자 잃음. min version + 부드러운 prompt.
- **Schema diff 없는 prod release**: breaking 사고.
## 🤖 LLM 활용 힌트
- URL versioning + Sunset header + 6+ 개월.
- CI 에서 schema diff 검사.
- Backwards-compatible 우선, breaking = new major.
## 🔗 관련 문서
- [[API_REST_Best_Practices]]
- [[API_OpenAPI_Spec]]
- [[Backend_Feature_Flags_Deep]]