Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

API versioning: URL, header, content negotiation

API and Technical Fundamentals cho BA Bài 21/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là BA của một công ty fintech ở TP.HCM. Cách đây sáu tháng, đội kỹ thuật mở một API cho phép các đối tác ví điện tử truy vấn số dư tài khoản. API chạy ổn, ba ngân hàng đối tác đã tích hợp. Hôm nay, business muốn đổi field customer_name thành hai field riêng first_namelast_name để phục vụ KYC. Một thay đổi "nhỏ" tưởng như vô hại. Nhưng ngay khi dev deploy, hệ thống của cả ba ngân hàng đối tác báo lỗi đồng loạt vì code của họ vẫn đang đọc field customer_name cũ — field đó giờ biến mất. Một sáng thứ Hai, ba cuộc gọi khẩn cấp.

Đây chính xác là lý do API versioning tồn tại. Một khi API đã được công bố (public) và có người dùng nó (consumer), bạn không còn toàn quyền sửa đổi nó nữa. API trở thành một hợp đồng (contract) — và bên kia hợp đồng đang viết code dựa vào đúng những gì bạn cam kết hôm qua. Versioning là nghệ thuật tiến hóa API theo thời gian mà không "đập vỡ" những người đang phụ thuộc vào phiên bản cũ.

Là BA, bạn thường là người đầu tiên nhận yêu cầu thay đổi từ business, và là người viết spec để dev triển khai. Nếu bạn không hiểu khi nào một thay đổi là "breaking" và chiến lược versioning nào phù hợp, bạn sẽ vô tình ký vào một spec gây sập tích hợp của khách hàng. Bài này giúp bạn phân biệt được điều đó và nói cùng ngôn ngữ với dev.

Khái niệm cốt lõi

Breaking change vs Non-breaking change

Trái tim của versioning là phân biệt được hai loại thay đổi:

Breaking change (thay đổi phá vỡ) — làm code của consumer hiện tại ngừng hoạt động đúng. Ví dụ:

  • Đổi tên field: customer_namecustomer_full_name
  • Xóa một field hoặc một endpoint
  • Đổi kiểu dữ liệu: price từ số (19000) thành chuỗi ("19,000")
  • Thêm một field bắt buộc (required) vào request — request cũ thiếu field này sẽ bị từ chối
  • Đổi ý nghĩa/đơn vị: amount trước tính bằng đồng, giờ tính bằng nghìn đồng
  • Đổi status code hoặc cấu trúc lỗi trả về
Non-breaking change (thay đổi tương thích ngược) — consumer cũ vẫn chạy bình thường. Ví dụ:
  • Thêm một field mới, không bắt buộc vào response (consumer cũ chỉ việc bỏ qua)
  • Thêm một endpoint hoàn toàn mới
  • Thêm một giá trị optional vào request
  • Thêm một status code mới cho trường hợp chưa từng xảy ra
Nguyên tắc vàng: chỉ những breaking change mới cần version mới. Nếu bạn chỉ thêm field optional, đừng tạo v2 — như vậy là lãng phí và gây phình to hệ thống. Rất nhiều BA mới vào nghề đề xuất bump version cho mọi thay đổi, đó là dấu hiệu chưa nắm vững khái niệm này.

Semantic versioning — cách đặt số phiên bản

Giới kỹ thuật thường dùng quy ước SemVer: MAJOR.MINOR.PATCH (ví dụ 2.4.1).

  • MAJOR (số đầu): tăng khi có breaking change.
  • MINOR: tăng khi thêm tính năng nhưng vẫn tương thích ngược.
  • PATCH: tăng khi sửa lỗi nhỏ, không đổi hợp đồng.
Trong API public, người ta thường chỉ phơi bày phần MAJOR ra ngoài URL hoặc header (ví dụ v1, v2), vì consumer chỉ cần biết "phiên bản này có phá vỡ tương thích hay không". Phần minor/patch được quản lý nội bộ.

Ba cách triển khai versioning

Có ba kỹ thuật phổ biến để consumer chỉ định họ muốn dùng phiên bản nào. Đây là phần cốt lõi nhất của bài.

#### 1. URL versioning (URI path)

Đưa version thẳng vào đường dẫn:

GET https://api.vietcredit.vn/v1/customers/123
GET https://api.vietcredit.vn/v2/customers/123

Ưu điểm: Dễ nhìn, dễ test bằng trình duyệt hay Postman, dễ debug. Khi đọc log bạn thấy ngay request gọi version nào. Đây là cách phổ biến nhất, được Twitter, GitHub (một thời), Stripe (một phần) dùng.

Nhược điểm: Về mặt lý thuyết REST thuần túy, một resource (khách hàng số 123) chỉ nên có một URL duy nhất — đặt version vào URL khiến cùng một khách hàng có hai địa chỉ v1/...v2/.... Ngoài ra khi nâng version, client phải sửa lại toàn bộ URL.

#### 2. Header versioning (custom header)

Version nằm trong một HTTP header riêng, URL giữ nguyên:

GET https://api.vietcredit.vn/customers/123
X-API-Version: 2

Ưu điểm: URL "sạch", một resource một URL. Linh hoạt khi muốn route nội bộ theo version.

Nhược điểm: "Ẩn" — không nhìn thấy version khi xem URL, khó test nhanh bằng trình duyệt, dễ bị quên khi viết tài liệu cho đối tác. Đối tác ít kinh nghiệm hay quên gắn header này.

#### 3. Content negotiation (Accept header / media type versioning)

Đây là cách "đúng REST" nhất. Version được nhúng vào kiểu media trong header Accept:

GET https://api.vietcredit.vn/customers/123
Accept: application/vnd.vietcredit.v2+json

Chuỗi vnd.vietcredit.v2+json nghĩa là: "tôi muốn nhận một biểu diễn (representation) của resource này theo định dạng JSON phiên bản 2 của nhà cung cấp vietcredit". GitHub API dùng đúng kiểu này (application/vnd.github+json kèm header version). Phần vnd viết tắt của "vendor" — nhà cung cấp.

Ưu điểm: Trung thành với triết lý REST — cùng một resource, chỉ là yêu cầu biểu diễn khác nhau. URL bất biến.

Nhược điểm: Phức tạp nhất, khó giải thích cho đối tác không chuyên, khó test thủ công, và nhiều dev cũng thấy "over-engineering" cho phần lớn dự án.

Vậy chọn cái nào?

Không có đáp án tuyệt đối, nhưng kinh nghiệm thực tế:

  • Với phần lớn API thương mại, đặc biệt khi đối tác là các công ty vừa và nhỏ ở Việt Nam, URL versioning thắng vì sự đơn giản và dễ tài liệu hóa.
  • Header/content negotiation phù hợp khi bạn xây hệ sinh thái lớn, đội kỹ thuật mạnh, và muốn giữ chuẩn REST nghiêm ngặt.

Tình huống thực tế

Tình huống 1 — Sàn TMĐT "ShopViet" và cú đổi field định mệnh

ShopViet (giả định) là sàn thương mại điện tử có 200 nhà bán hàng tích hợp API để đồng bộ đơn hàng. API v1 trả về đơn hàng với field total (tổng tiền, tính bằng VNĐ). Business muốn tách thành subtotal, shipping_fee, total để minh bạch hơn, đồng thời đổi total từ số nguyên sang object { "amount": 199000, "currency": "VND" }.

Đội BA ban đầu định sửa thẳng trên v1 cho nhanh. May mắn, một BA cảnh giác chỉ ra: đổi total từ số sang object là breaking change — toàn bộ 200 nhà bán đang đọc order.total như một con số sẽ crash. Họ quyết định:

  • Giữ nguyên v1 chạy y hệt cũ.
  • Mở v2 với cấu trúc mới qua URL: /v2/orders.
  • Gửi email thông báo trước 90 ngày, đặt deadline ngừng hỗ trợ (deprecation) v1 sau 12 tháng.
Diễn giải: Cái khó không phải kỹ thuật, mà là quản lý vòng đời. Họ phải duy trì song song hai version trong suốt 12 tháng — tốn chi phí, nhưng đổi lại không một nhà bán nào bị gián đoạn.

Bài học: Đổi kiểu dữ liệu của một field luôn là breaking change. Versioning không chỉ là tạo URL mới, mà là cam kết duy trì phiên bản cũ đủ lâu cho consumer kịp di chuyển.

Tình huống 2 — Cổng thanh toán và header versioning bị "bỏ quên"

Một cổng thanh toán nội địa chọn header versioning (X-Payment-API-Version) cho gọn URL. Nghe rất hợp lý về kiến trúc. Nhưng ba tháng sau, đội support ngập trong ticket: nhiều merchant nhỏ quên gắn header, hệ thống mặc định route về v1 cũ, dẫn đến họ không nhận được field qr_code mới chỉ có ở v2. Merchant tưởng API lỗi.

Đội kỹ thuật xử lý bằng cách: khi thiếu header version, trả về cảnh báo rõ ràng trong response ("warning": "Bạn đang dùng v1 mặc định, vui lòng chỉ định X-Payment-API-Version") thay vì âm thầm fallback.

Bài học: Header versioning "đẹp" về lý thuyết nhưng đắt về vận hành nếu đối tác của bạn không chuyên. BA nên cân nhắc trình độ kỹ thuật của consumer khi tư vấn chiến lược, chứ không chỉ chọn cái "chuẩn nhất".

Tình huống 3 — GitHub và content negotiation

GitHub API dùng content negotiation: client gửi Accept: application/vnd.github+json và có thể chỉ định version qua header X-GitHub-Api-Version: 2022-11-28 (họ dùng ngày tháng làm version thay vì số — một biến thể thú vị). Cách này cho phép GitHub phục vụ hàng triệu developer với nhiều phiên bản API mà URL của mỗi resource vẫn duy nhất.

Bài học: Với hệ sinh thái cực lớn và developer chuyên nghiệp, content negotiation và versioning theo ngày là hợp lý. Nhưng hãy nhớ: GitHub có cả một đội ngũ devrel và tài liệu khổng lồ để hỗ trợ. Đừng sao chép mô hình của họ cho một startup 10 người mà không cân nhắc chi phí.

Hướng dẫn từng bước

Khi business yêu cầu một thay đổi trên API đã public, BA nên đi theo quy trình:

  • Phân loại thay đổi. Tự hỏi: thay đổi này có làm code consumer hiện tại ngừng chạy không? Đối chiếu danh sách breaking/non-breaking ở trên. Nếu non-breaking → không cần version mới, chỉ cần thêm vào phiên bản hiện tại và cập nhật tài liệu.
  • Nếu là breaking change, xác định chiến lược versioning. Nếu dự án đã có quy ước (ví dụ đã dùng URL /v1/), tuân theo quy ước đó để nhất quán. Đừng trộn lẫn URL versioning chỗ này, header versioning chỗ kia.
  • Lên kế hoạch song hành (parallel run). Phiên bản cũ phải tiếp tục chạy. Xác định: duy trì bao lâu? Ai chịu chi phí maintain?
  • Viết deprecation policy rõ ràng. Thông báo trước bao nhiêu ngày? Gửi qua kênh nào (email, dashboard, header DeprecationSunset theo chuẩn HTTP)? Ngày tắt hẳn (sunset date) là khi nào?
  • Cập nhật tài liệu cho cả hai version. Mỗi version cần changelog ghi rõ cái gì thay đổi và cách migrate.
  • Thiết kế default behavior. Khi consumer không chỉ định version, hệ thống làm gì? Khuyến nghị: KHÔNG tự nâng version mặc định cho họ (sẽ vô tình gây breaking), mà giữ ở version cũ nhất được hỗ trợ hoặc bắt buộc khai báo tường minh.
  • Theo dõi tỉ lệ sử dụng từng version. Trước khi tắt v1, kiểm tra log: còn bao nhiêu % traffic dùng v1? Nếu còn 30% thì chưa thể tắt.

Lỗi thường gặp & mẹo

Lỗi 1 — Bump version cho mọi thay đổi. Thêm một field optional mà cũng tạo v2 là phí phạm. Hãy hỏi dev: "thay đổi này có breaking không?" trước khi đề xuất version mới.

Lỗi 2 — Tạo version mới rồi tắt version cũ ngay. Versioning vô nghĩa nếu bạn không cho consumer thời gian di chuyển. Một version mới mà ép tắt cũ sau 1 tuần thì chẳng khác gì breaking change trực diện.

Lỗi 3 — Không có deprecation policy. Mở v1, v2, v3... rồi duy trì tất cả mãi mãi vì không ai dám tắt. Sau vài năm, đội kỹ thuật phải maintain 5 version song song, chi phí khổng lồ. Phải có lộ trình khai tử rõ ràng ngay từ đầu.

Lỗi 4 — Âm thầm fallback về version cũ. Như tình huống cổng thanh toán: khi consumer quên chỉ định version, đừng lặng lẽ route về v1 mà không báo. Hãy trả cảnh báo hoặc lỗi rõ ràng.

Lỗi 5 — Trộn nhiều cách versioning. Một dự án chỗ thì /v1/ trên URL, chỗ thì header, gây rối loạn cho cả dev lẫn đối tác. Chọn một cách và dùng nhất quán toàn hệ thống.

Mẹo: Dùng header chuẩn HTTP Deprecation: trueSunset: Sat, 31 Dec 2026 23:59:59 GMT để máy móc của consumer tự phát hiện version sắp bị khai tử — văn minh hơn nhiều so với chỉ gửi email.

Mẹo: Khi viết spec, luôn kèm một bảng "ma trận thay đổi" liệt kê từng field, đánh dấu rõ field nào breaking, field nào không. Bảng này giúp dev và stakeholder thống nhất nhanh.

Bài tập thực hành

  • Phân loại nhanh. Với mỗi thay đổi sau, đánh dấu Breaking (B) hay Non-breaking (N):
- (a) Thêm field optional loyalty_points vào response đơn hàng. - (b) Đổi phone từ chuỗi "0901234567" sang object { "country_code": "+84", "number": "901234567" }. - (c) Thêm một endpoint mới /v1/refunds. - (d) Làm field email trở thành bắt buộc trong request tạo tài khoản. - (e) Sửa lỗi chính tả trong message lỗi trả về.

  • Thiết kế URL. Một API quản lý khóa học cần nâng cấp breaking. Viết URL v1v2 cho endpoint "lấy chi tiết khóa học id 88" theo phong cách URL versioning.
  • Viết deprecation note. Soạn một đoạn thông báo (3–4 câu) gửi đối tác báo rằng v1 sẽ ngừng hỗ trợ sau 6 tháng, kèm ngày sunset cụ thể và hướng dẫn họ cần làm gì.
  • Tình huống mở. Công ty bạn có 5 đối tác lớn (rất chuyên về kỹ thuật) và 50 đối tác nhỏ (kỹ thuật yếu). Bạn sẽ tư vấn dùng cách versioning nào? Vì sao? (Gợi ý: cân nhắc khả năng tài liệu hóa và hỗ trợ.)
Đáp án gợi ý bài 1: (a) N, (b) B, (c) N, (d) B, (e) N.

Tóm tắt

  • API public là một hợp đồng với consumer; versioning cho phép tiến hóa hợp đồng đó mà không phá vỡ người đang dùng.
  • Chỉ breaking change mới cần version mới: đổi tên/xóa field, đổi kiểu dữ liệu, thêm field bắt buộc, đổi ý nghĩa. Thêm field optional hay endpoint mới là non-breaking — không cần bump version.
  • Ba cách triển khai: URL (dễ dùng, phổ biến nhất, hợp với đối tác VN), header (URL sạch nhưng dễ bị quên), content negotiation (chuẩn REST nhất nhưng phức tạp).
  • Versioning không chỉ là tạo URL mới — nó là cam kết duy trì song song phiên bản cũ và có deprecation policy rõ ràng (thông báo trước, sunset date, theo dõi traffic).
  • Là BA, vai trò của bạn là phân loại đúng loại thay đổi, chọn chiến lược phù hợp với trình độ consumer, và viết spec kèm lộ trình migrate — để một sáng thứ Hai không có ba cuộc gọi khẩn cấp nào cả.