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

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
vibe-coding
language applicable_to
TS
Backend
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
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.

🔗 관련 문서