5.5 KiB
5.5 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| api-versioning-strategies | API Versioning — URL / Header / Date 전략 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
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
app.use('/v1', v1Router);
app.use('/v2', v2Router);
장점: 간단, 캐시 친화, 명확.
단점: URL 변경 = client 코드 변경.
Header versioning
GET /orders
Accept: application/vnd.acme.v2+json
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 유지.
// 변환 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 변경
// ✅ 새 필드 추가
{ 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"
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
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 (소규모)
// 응답 안 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
// 사용자가 너무 옛 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
type Order {
id: ID!
status: OrderStatus!
oldField: String @deprecated(reason: "Use newField")
newField: String
}
→ Breaking 없이 점진.
Schema breaking 검출 (CI)
# 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.