Bỏ qua đến nội dung chính
API versioningAPI designRESTbackwards compatibilityAPI

API Versioning: Chiến Lược Quản Lý Thay Đổi Cho Production

API versioning chiến lược: URL prefix vs header vs query param, breaking change, deprecation timeline, migration guide. Cách chọn đúng cho dự án.

Xuất bản 8 phút đọc

Sớm hay muộn API anh sẽ cần thay đổi. API versioning đúng chiến lược giúp anh evolve mà không phá client cũ. Bài này phân tích 3 cách version + chiến lược deprecation thực tế từ Stripe, GitHub, Twilio.

Vì sao cần version?

API là contract giữa anh và client. Một khi public, anh không control client nâng cấp. Mobile app v1.0 vẫn còn người dùng năm sau. Partner integrate xong khó bảo họ refactor mỗi tháng.

Không version = mỗi thay đổi breaking đều có thể phá client cũ. Có version = anh đẩy v2 song song, deprecate v1 dần dần.

Cái gì là breaking change?

Breaking (cần bump major)Non-breaking (giữ version)
Xoá field từ responseThêm field mới vào response
Đổi tên fieldThêm endpoint mới
Đổi type field (string → int)Thêm query param optional
Đổi nghĩa enum valueMở rộng enum (thêm value mới)*
Xoá endpointLoosen validation (cho phép input rộng hơn)
Thêm required field inputOptional field input
Đổi error codeSửa lỗi rõ ràng (typo, perf)

* Cảnh báo: enum mở rộng là breaking nếu client làm switch không có default branch — nó sẽ rơi vào exception. Document rõ "enum values may be added".

3 cách versioning

1. URL prefix — rõ ràng nhất

https://api.alodev.vn/v1/users
https://api.alodev.vn/v2/users

Ưu:

  • Dễ debug — chỉ nhìn URL biết version
  • Dễ route ở reverse proxy: nginx location /v1/, location /v2/
  • Cache HTTP rõ — mỗi version là URL riêng
  • Stripe, Twilio, Square dùng cách này

Nhược:

  • URL không "canonical" — same resource có 2+ URL
  • Refactor lớn khi bump major — phải copy code gần như toàn bộ

2. Header — sạch URL

GET /users HTTP/1.1
Accept: application/vnd.alodev.v2+json

Hoặc custom header:

GET /users HTTP/1.1
X-API-Version: 2

Ưu: URL canonical không đổi. Nhược: khó test bằng browser/curl, khó cache CDN, khó debug. GitHub đã dùng cách này (Accept: application/vnd.github.v3+json) nhưng đa số dev vẫn confused.

3. Query param — không khuyến cáo

GET /users?v=2

Tệ nhất. Lẫn với filter, dễ quên, không clean. Tránh.

Khi nào bump version?

Quy tắc Stripe: chỉ bump khi buộc phải. 90% thay đổi là non-breaking. Bump major là cuộc tốn:

  • Maintain song song nhiều version (DB schema, business logic)
  • Documentation viết lại
  • Migration guide cho client
  • Email + announcement

Trước khi bump, hỏi: có cách nào làm non-breaking không?

// Thay vì đổi field user.name thành full_name (breaking)
// Trả cả 2:
{
  "id": 1,
  "name": "An Nguyen",         // legacy
  "full_name": "An Nguyen",    // new field
  // ... và document deprecate "name" trong 12 tháng
}

Deprecation timeline

Khi đã quyết bump v2, plan deprecation v1:

Tháng 0: Launch v2. v1 vẫn hoạt động bình thường.
Tháng 0: Email + blog post công bố v2 + migration guide.
Tháng 0+: API v1 trả header Sunset + Deprecation:
   Deprecation: true
   Sunset: Sat, 31 Dec 2026 23:59:59 GMT
   Link: <https://docs.alodev.vn/migrate-v2>; rel="deprecation"

Tháng 3: Email reminder cho client còn dùng v1 (theo API key).
Tháng 6: Status page banner.
Tháng 9: Email final warning. Statuspage incident.
Tháng 12: Tắt v1, trả 410 Gone với link migration guide.

Header Sunset là chuẩn RFC 8594. Client library tốt sẽ tự log warning.

Implement Express với 2 version

// Tách router theo version
import { Router } from 'express'

const v1 = Router()
v1.get('/users', listUsersV1)        // schema cũ
v1.post('/users', createUserV1)

const v2 = Router()
v2.get('/users', listUsersV2)        // schema mới
v2.post('/users', createUserV2)

// Middleware add deprecation header cho v1
v1.use((req, res, next) => {
  res.setHeader('Deprecation', 'true')
  res.setHeader('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT')
  res.setHeader('Link', '<https://docs.alodev.vn/migrate-v2>; rel="deprecation"')
  next()
})

app.use('/v1', v1)
app.use('/v2', v2)

Code có thể share — controller v2 có thể call lại logic v1 nếu chỉ thêm field. Đừng duplicate business logic — chỉ duplicate adapter (request/response shape).

GraphQL evolve schema

GraphQL không version cả endpoint — schema evolve field-level:

type User {
  id: ID!
  name: String! @deprecated(reason: "Use fullName instead")
  fullName: String!
  email: String!
}

Client cũ vẫn query name — vẫn hoạt động. Client mới query fullName. GraphiQL hiện cảnh báo deprecation. Sau 12 tháng, anh có thể xoá name nếu không còn ai query (kiểm tra qua usage log).

Đây là một trong những lý do team chọn GraphQL vs REST — evolve schema mềm hơn nhiều.

Checklist API versioning đúng chuẩn

  • ✅ Mọi API mới bắt đầu với /v1/ prefix
  • ✅ Document rõ contract: OpenAPI spec cho v1, v2
  • ✅ Bump major chỉ khi không thể non-breaking
  • ✅ Header Deprecation + Sunset khi version cũ tắt sắp tắt
  • ✅ Migration guide có ví dụ trước/sau
  • ✅ Usage log theo version để biết khi nào tắt được v1 an toàn
  • ✅ Email + status page communication 6-12 tháng

Kết luận

API versioning chiến lược không phức tạp — chỉ cần kỷ luật. Bắt đầu /v1/, evolve non-breaking khi có thể, bump v2 với deprecation timeline rõ. Anh sẽ cảm ơn bản thân khi 2 năm sau cần thay đổi mà không phá hệ thống của khách hàng. Kết hợp với REST API design chuẩn để có nền tảng vững từ đầu.

Zalo