LabHub

블로그

API 버저닝 & 진화 전략 완전 가이드 2025: Breaking Changes 없는 API 진화, Deprecation, Sunset

한국어English日本語

TL;DR


1. API 진화의 본질적 어려움

1.1 외부 API의 운명

"한 번 공개된 API는 영원히 살아있다." — Hyrum's Law

내부 코드는 마음대로 리팩토링할 수 있습니다. 외부 API는 다릅니다:

1.2 변경의 종류

변경영향호환성
새 엔드포인트 추가없음
새 응답 필드 추가없음
선택 필드 추가없음
응답 필드 제거Breaking
필드 이름 변경Breaking
필드 타입 변경Breaking
필수 필드 추가Breaking
에러 코드 의미 변경Breaking
기본 동작 변경Breaking

규칙: 추가는 안전, 제거/변경은 위험.

1.3 Hyrum's Law의 잔혹함

"API에 충분한 사용자가 있으면, 어떤 관찰 가능한 동작도 누군가는 의존한다."

실제 사례:

결론: "공식 문서에 없는 것은 바꿔도 된다"는 잘못입니다. 모든 관찰 가능한 동작이 API의 일부입니다.


2. 버저닝 전략 4가지

2.1 URL Path Versioning

GET /v1/users/123
GET /v2/users/123

장점:

단점:

채택: GitHub (/v3/), Twitter, Stripe (URL은 /v1/이지만 실제는 헤더로)

2.2 Header Versioning

GET /users/123 HTTP/1.1
Stripe-Version: 2024-04-15

장점:

단점:

채택: Stripe (가장 유명), Azure

2.3 Content Negotiation

GET /users/123 HTTP/1.1
Accept: application/vnd.example.user.v2+json

장점:

단점:

채택: GitHub (선택적, Accept: application/vnd.github.v3+json)

2.4 Query Parameter

GET /users/123?version=2
GET /users/123?api_version=2024-04-15

장점:

단점:

채택: 일부 단순 API

2.5 비교표

방식URL 깨끗디버깅캐시채택
URL Path⭐⭐⭐⭐⭐⭐GitHub, Twitter
Header⭐⭐⭐⭐⭐Stripe, Azure
Content Negotiation⭐⭐⭐⭐⭐⭐⭐⭐GitHub (옵션)
Query Param⭐⭐⭐⭐⭐⭐⭐단순 API

3. Stripe의 천재적인 날짜 기반 버저닝

3.1 핵심 아이디어

Stripe는 버전을 날짜로 표현합니다:

모든 변경은 날짜로 식별됩니다.

3.2 클라이언트 사용

import stripe

# 계정 기본 버전 사용
stripe.api_version = "2024-04-15"

# 또는 요청별
stripe.Charge.create(
    amount=2000,
    currency="usd",
    api_version="2023-10-16"  # 옛 동작
)

3.3 Stripe의 비결 — 변환 레이어

서버에는 단 하나의 코드베이스 (최신 버전).

각 요청에 대해:

  1. 클라이언트 버전 확인
  2. 요청을 최신 버전으로 변환 (forward transform)
  3. 처리
  4. 응답을 클라이언트 버전으로 변환 (backward transform)
클라이언트 (v2020)[변환] → 최신 코드 → [변환]클라이언트 (v2020)

효과:

3.4 변환 예시

v2020에서 v2024 변경: 응답에 description 필드 추가.

# 변환 함수
def transform_to_v2020(response):
    if "description" in response:
        del response["description"]  # v2020 클라이언트는 이 필드를 모름
    return response

v2020에서 v2024 변경: amount가 정수에서 객체로 변경.

def transform_to_v2020(response):
    if isinstance(response.get("amount"), dict):
        response["amount"] = response["amount"]["value"]
    return response

3.5 Stripe 변경 로그

각 버전 변경이 정확히 문서화됩니다:

2024-04-15

  • Added description field to Charge object
  • amount field type changed from integer to AmountObject
  • Default currency is now derived from account settings

Migration guide: ...

이 정도의 투명성이 신뢰의 핵심입니다.


4. GraphQL의 버전 없는 진화

4.1 핵심 철학

GraphQL은 버전을 사용하지 않습니다. 대신 필드 단위로 진화:

type User {
  id: ID!
  name: String!
  
  # Deprecated 필드
  email: String @deprecated(reason: "Use 'emailAddress' instead. Will be removed 2025-12-31")
  emailAddress: String!
}

4.2 클라이언트가 정확히 요청

query {
  user(id: "123") {
    id
    name
    emailAddress  # 새 필드만 요청
  }
}

기존 클라이언트는 email을 계속 요청 → 작동. 새 클라이언트는 emailAddress 사용.

Over-fetching이 없다 = 새 필드 추가가 무료.

4.3 Deprecation 추적

GraphQL 서버가 사용 통계를 수집:

Apollo Studio, Hasura Cloud가 이 기능 제공.

4.4 GraphQL의 한계

채택: GitHub, Shopify, Twitter, Airbnb


5. Semantic Versioning과 API

5.1 SemVer 기본

MAJOR.MINOR.PATCH
v1.2.3

5.2 라이브러리 vs API

라이브러리: SemVer 자연스러움.

Web API: 적용 어려움.

현실: Web API는 보통 메이저 버전만 표시 (/v1, /v2).

5.3 SemVer의 한계

v2.0.0이 나오면 모든 사용자가 마이그레이션 필요. 점진적 진화가 어려움.

Stripe 방식 (날짜 기반) 또는 GraphQL 방식 (버전 없음)이 더 우아.


6. Deprecation과 Sunset

6.1 Deprecation 단계

  1. Announce: 블로그, 이메일, 변경 로그
  2. Mark in API: 응답 헤더 또는 필드
  3. Monitor: 사용 추적
  4. Reminder: 사용 클라이언트에 직접 알림
  5. Sunset: 폐기 (HTTP 410 Gone)

6.2 Deprecation 헤더

RFC 8594: HTTP Sunset 헤더

HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: Sat, 31 Dec 2024 23:59:59 GMT
Link: <https://api.example.com/docs/migration>; rel="deprecation"

의미:

6.3 Deprecation 메시지 (응답 본문)

{
  "data": {...},
  "warnings": [
    {
      "code": "DEPRECATED_FIELD",
      "message": "Field 'email' is deprecated. Use 'emailAddress' instead.",
      "documentation_url": "https://api.example.com/docs/v2#email-deprecation",
      "sunset_date": "2025-12-31"
    }
  ]
}

6.4 사용자에게 알리기

기술적:

커뮤니케이션:

Stripe: 사용 중인 deprecated API에 대해 자동 이메일 발송.

6.5 Sunset 정책 예시

사용자Sunset 기간
무료 사용자6개월
유료 사용자1년
엔터프라이즈2년

큰 회사일수록 변경에 시간 필요.


7. Breaking Changes 회피 전략

7.1 Additive Changes 우선

잘못된 변경:

- "user_email"
+ "email"

올바른 변경:

+ "email"  // 새 필드 추가
  "user_email"  // 옛 필드 유지 (deprecated)

두 필드를 같이 반환. 클라이언트가 마이그레이션할 시간 줌.

7.2 새 엔드포인트 vs 기존 변경

Bad: 기존 /users 응답 형식 변경.

Good: 새 /v2/users 엔드포인트 또는 /users?format=new.

7.3 Default 값 신중히

// v1
{ "page_size": 20 }  // 기본값 20

// v2 — 50으로 변경?
{ "page_size": 50 }  // ❌ Breaking! (페이지네이션 동작 변경)

기본값 변경은 종종 breaking입니다.

7.4 Optional → Required는 Breaking

- email: string?  // optional
+ email: string   // required

기존 클라이언트가 email을 보내지 않으면 실패. 추가하지 마세요.

7.5 Enum 값 추가는 안전?

enum Status {
  ACTIVE,
  INACTIVE,
+ PENDING_REVIEW  // 새 값 추가
}

미묘함: 클라이언트가 enum을 switch로 처리하면 새 값에 대한 default 케이스가 필요. 있다면 안전, 없다면 미묘한 버그.

조언: enum 추가는 기술적으로 backward compatible이지만, 클라이언트 코드 검토 필요.


8. 실제 API의 진화 사례

8.1 Stripe — 날짜 기반의 우아함

8.2 GitHub — REST에서 GraphQL로

교훈: 기존 API는 그대로 두고, 새 패러다임은 별도 시작.

8.3 Twilio — 메이저 버전 + 점진적 마이그레이션

8.4 Slack — 점진적 deprecation

8.5 AWS — 거의 절대 깨지 않음


9. 모범 사례 체크리스트

9.1 설계 단계

9.2 변경 시

9.3 Sunset 시


10. API 진화의 미래

10.1 OpenAPI 3.1 + JSON Schema

스키마를 통한 자동 호환성 검사:

10.2 AI 기반 마이그레이션

10.3 contract testing 표준화


퀴즈

1. 가장 흔한 Breaking Change는?

: 응답 필드 제거 또는 이름 변경입니다. 클라이언트가 그 필드를 사용 중이라면 즉시 깨집니다. 안전한 대안: 새 필드를 추가하고 기존 필드를 deprecated로 표시 (둘 다 반환). 일정 기간 후 제거. 다른 흔한 breaking changes: 필드 타입 변경(string → object), 필수 필드 추가, 기본값 변경, enum 값 의미 변경.

2. Stripe의 날짜 기반 버저닝의 장점은?

: (1) 점진적 마이그레이션 — 클라이언트가 자기 페이스로 새 버전 채택, (2) 단일 코드베이스 — 서버는 최신 버전만 유지, 변환 레이어로 옛 클라이언트 지원, (3) 명확한 변경 로그 — 각 날짜의 변경 사항이 정확히 문서화, (4) 테스트 용이 — 특정 날짜 버전을 명시적으로 테스트. 단점은 변환 레이어 구현이 복잡하다는 것.

3. GraphQL이 버전을 사용하지 않는 이유는?

: GraphQL은 클라이언트가 정확히 필요한 필드만 요청하므로, 새 필드 추가가 기존 클라이언트에 영향 없습니다 — 그들은 그 필드를 요청하지 않으니까. 필드 제거는 @deprecated로 마크하고, 사용 통계를 추적하여 안전하게 제거 가능. 결과: 버전 없이 영원히 진화하는 API. 단점은 모든 변경이 호환되는 건 아니라는 것 (타입 변경, enum 값 제거 등).

4. Sunset 헤더의 역할은?

: RFC 8594 표준 HTTP 헤더로, "이 리소스가 언제 폐기될 것인가"를 알려줍니다. Sunset: Sat, 31 Dec 2025 23:59:59 GMT. 클라이언트는 이 헤더를 보고 마이그레이션 일정을 자동으로 인식할 수 있습니다. Deprecation 헤더와 Link 헤더(마이그레이션 가이드)와 함께 사용. 자동화된 클라이언트가 안전하게 폐기 일정에 대응할 수 있게 합니다.

5. Hyrum's Law가 API 설계에 의미하는 것은?

: "API에 충분한 사용자가 있으면, 어떤 관찰 가능한 동작도 누군가는 의존한다." 즉, 공식 문서에 없어도 변경하면 안 됩니다. 응답 필드 순서, 에러 메시지의 정확한 텍스트, 응답 시간, ID의 순차성 등 모두가 "API의 일부"가 됩니다. 결론: (1) 처음부터 신중하게 설계, (2) 변경 시 모든 관찰 가능한 동작을 고려, (3) 의도하지 않은 동작은 명시적으로 "이 동작에 의존하지 마세요"라고 문서화.


참고 자료

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다