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 ví điện tử. Một sáng đẹp trời, team backend deploy phiên bản mới của API thanh toán: họ đổi tên field amount thành total_amount cho "rõ nghĩa hơn". Code chạy ngon trên môi trường staging, test pass hết. Nhưng 30 phút sau khi lên production, tổng đài bắt đầu nổ tung: hàng loạt merchant tích hợp với ví của bạn báo lỗi "không nhận được số tiền giao dịch". Các app POS ngoài cửa hàng treo cứng. Một thay đổi tưởng như vô hại đã làm gãy hàng trăm hệ thống đối tác mà bạn không hề kiểm soát.
Đây chính là lý do tại sao API Versioning (quản lý phiên bản API) và Backward Compatibility (tương thích ngược) là một trong những kỹ năng sống còn của Technical BA. Khi một API đã được publish và có "khách hàng" sử dụng — dù là app mobile của chính công ty, hay hệ thống của đối tác bên ngoài — thì mỗi thay đổi đều có thể gây ra hậu quả dây chuyền. Không giống như sửa một màn hình UI (người dùng nhìn thấy ngay và thích nghi được), API là một hợp đồng (contract) giữa các hệ thống. Bên gọi (consumer) viết code dựa trên đúng cái hợp đồng đó. Phá vỡ hợp đồng mà không báo trước thì giống như đổi ổ cắm điện cả nước trong một đêm.
Là BA, bạn hiếm khi tự code, nhưng bạn là người viết spec quyết định API sẽ thay đổi thế nào, và quan trọng hơn — bạn là người phải trả lời câu hỏi: "Thay đổi này có làm gãy ai không? Nếu có, ta xử lý ra sao?" Hiểu rõ phần này giúp bạn viết spec an toàn, đàm phán với đối tác hợp lý, và tránh những sự cố production tốn kém cả về tiền lẫn uy tín.
Khái niệm cốt lõi
Backward Compatibility là gì?
Một thay đổi được gọi là tương thích ngược (backward-compatible) khi các consumer cũ — những hệ thống đã viết code theo phiên bản API trước — vẫn tiếp tục chạy bình thường mà không cần sửa một dòng code nào. Ngược lại, một thay đổi breaking (phá vỡ) là khi consumer cũ sẽ lỗi nếu không cập nhật.
Nguyên tắc vàng để phân biệt rất đơn giản, dựa trên góc nhìn của bên gọi API:
- Thêm cái gì đó mới mà bên cũ có thể bỏ qua → an toàn (additive).
- Đổi hoặc bỏ cái gì đó mà bên cũ đang dựa vào → nguy hiểm (breaking).
Nhóm 1 — Thay đổi tương thích ngược (Additive / Non-breaking)
Đây là những thay đổi "cộng thêm", không động vào cái đang có. Ví dụ điển hình:
- Thêm một field optional vào response. Ví dụ response trả thêm field
loyalty_points. Consumer cũ không biết field này tồn tại, họ chỉ đọc các field họ cần và bỏ qua phần dư. Không ai gãy. - Thêm một endpoint mới hoàn toàn. Ví dụ thêm
GET /v1/orders/{id}/timeline. Endpoint cũ vẫn nguyên vẹn, ai chưa dùng cái mới thì không bị ảnh hưởng. - Thêm một parameter optional vào request, với điều kiện nếu không truyền thì hệ thống vẫn hoạt động như trước (có giá trị mặc định hợp lý).
- Thêm một giá trị enum mới — nhưng cái này có "bẫy", ta sẽ nói ở phần lỗi thường gặp.
- Nới lỏng ràng buộc validation. Ví dụ trước đây
notetối đa 100 ký tự, giờ cho phép 500. Bên cũ gửi 100 ký tự vẫn pass.
Nhóm 2 — Thay đổi phá vỡ (Breaking changes)
Đây là những thay đổi khiến hợp đồng cũ không còn đúng. Cần nhận diện thật chắc:
- Đổi tên field (
amount→total_amount): consumer cũ tìmamountkhông thấy → lỗi. - Đổi kiểu dữ liệu (field
pricetừ số15000thành chuỗi"15,000", hoặc từ integer sang object): code cũ parse sai → lỗi. - Xóa field hoặc xóa endpoint: bên cũ gọi cái không còn tồn tại → 404 hoặc thiếu dữ liệu.
- Thêm một field bắt buộc (required) vào request: bên cũ không gửi field đó → bị validation từ chối, lỗi 400.
- Siết chặt validation: trước cho
note500 ký tự, giờ chỉ 100 — bên cũ đang gửi 300 ký tự sẽ bị từ chối. - Đổi ngữ nghĩa (semantics) dù không đổi cấu trúc: ví dụ field
status = "completed"trước nghĩa là "đã giao hàng", giờ đổi thành "đã thanh toán". Cấu trúc y hệt nhưng logic bên consumer hiểu sai hết. Đây là loại breaking nguy hiểm nhất vì test tự động khó bắt được. - Đổi format giá trị: ngày từ
dd/MM/yyyysangyyyy-MM-dd, hoặc tiền từ "đồng" sang "nghìn đồng".
Các chiến lược đặt phiên bản (Versioning)
Khi buộc phải tạo breaking change, ta cần một phiên bản mới để hai bên cũ và mới cùng tồn tại. Có vài cách đặt version phổ biến:
- URI versioning:
/v1/orders,/v2/orders. Dễ thấy, dễ test, đối tác dễ hiểu nhất — đây là cách phổ biến nhất ở Việt Nam và Đông Nam Á. - Header versioning: truyền version qua header như
Accept: application/vnd.company.v2+json. Gọn URL nhưng khó debug, đối tác hay quên set header. - Query parameter:
/orders?version=2. Đơn giản nhưng dễ bị bỏ sót.
Tình huống thực tế
Tình huống 1 — Ví điện tử và cú đổi tên field "chết người"
Một ví điện tử lớn tại Việt Nam (gọi là VPay) có API POST /v1/payment/notify gửi callback cho merchant mỗi khi có giao dịch. Response chứa field amount là số tiền. Sau 2 năm, team kỹ thuật muốn chuẩn hóa, đổi amount thành transaction_amount và bổ sung currency.
Một bạn dev mới, không có BA review spec kỹ, gộp cả việc đổi tên vào release thường ngày. Kết quả: hơn 200 merchant đang đọc field amount đột nhiên nhận null. Các shop bán hàng online không ghi nhận được đơn đã thanh toán, khách kêu "đã trả tiền mà shop bảo chưa". Sự cố kéo dài 3 tiếng, VPay phải rollback khẩn cấp và gửi email xin lỗi toàn bộ đối tác.
Bài học: Đổi tên field là breaking change kinh điển. Cách đúng phải là: giữ nguyên amount (additive), thêm song song transaction_amount và currency. Sau đó thông báo deprecation amount với lộ trình 12 tháng, theo dõi xem còn merchant nào dùng field cũ, hỗ trợ họ chuyển, rồi mới gỡ ở /v2. BA chính là người phải chặn pha "gộp đổi tên vào release thường" này ngay từ khâu review spec.
Tình huống 2 — Grab và bài học thêm enum mới
Một nền tảng gọi xe (lấy cảm hứng từ mô hình Grab/Gojek) có API trả về ride_status với các giá trị: pending, accepted, completed, cancelled. App đối tác cho tài xế và một số hệ thống doanh nghiệp tích hợp để theo dõi chuyến xe nhân viên.
Team sản phẩm thêm tính năng mới và bổ sung trạng thái driver_arriving (tài xế sắp đến). Họ nghĩ "thêm enum thì là additive, an toàn". Nhưng một hệ thống đối tác đã viết code kiểu: if status not in [4 giá trị cũ] then throw error. Khi gặp driver_arriving, hệ thống đó crash. Một đối tác khác thì dùng switch-case không có nhánh default, dẫn đến chuyến xe "biến mất" khỏi dashboard của họ.
Bài học: Thêm giá trị enum mới về lý thuyết là additive, nhưng thực tế là vùng xám. Nó chỉ an toàn nếu consumer được thiết kế để "khoan dung" với giá trị lạ. Vì BA không kiểm soát được code của đối tác, nên trong spec và tài liệu API, bạn phải ghi rõ ngay từ đầu: "Danh sách enum có thể được bổ sung trong tương lai; client phải xử lý gracefully các giá trị chưa biết." Và khi sắp thêm enum mới, hãy thông báo trước cho đối tác — đừng âm thầm.
Tình huống 3 — Ngân hàng và Open API cho đối tác fintech
Một ngân hàng tại Việt Nam mở Open Banking API cho các công ty fintech (cho vay, quản lý tài chính cá nhân) tích hợp. API GET /v1/accounts/{id}/balance trả về available_balance kiểu số nguyên (đồng).
Do yêu cầu hỗ trợ đa tiền tệ, ngân hàng muốn đổi available_balance thành object { "value": 5000000, "currency": "VND" }. Đây là đổi kiểu dữ liệu — breaking change nặng. Lần này họ làm bài bản: giữ nguyên /v1 cho 20+ fintech đang chạy, ra mắt /v2 với cấu trúc mới. Họ gửi thông báo deprecation /v1 trước 9 tháng, thêm header Sunset: Sat, 31 Dec 2026 23:59:59 GMT vào mọi response /v1, đồng thời dựng dashboard theo dõi lưu lượng từng fintech trên /v1. Đến gần deadline, có 3 fintech vẫn chưa chuyển; đội quan hệ đối tác gọi điện hỗ trợ trực tiếp, gia hạn thêm cho 2 đơn vị có lý do chính đáng. Cuối cùng tắt /v1 mượt mà, không sự cố.
Bài học: Với API cho đối tác bên ngoài, breaking change là không thể tránh khỏi, nhưng quy trình deprecation có kiểm soát mới là thứ phân biệt một tổ chức chuyên nghiệp với một mớ hỗn loạn. BA là người soạn lộ trình này: ai bị ảnh hưởng, thông báo khi nào, deadline bao lâu, đo lường ra sao.
Hướng dẫn từng bước
Khi bạn — với vai trò BA — nhận một yêu cầu thay đổi API, hãy đi theo các bước sau:
- Xác định ai đang dùng API (consumer). API này nội bộ (chỉ app của công ty), hay public cho đối tác? Nội bộ thì còn dễ phối hợp; public thì mọi thay đổi đều phải cực kỳ thận trọng. Liệt kê rõ các bên gọi.
- Phân loại thay đổi: additive hay breaking? Dùng checklist ở phần khái niệm. Tự hỏi: "Một consumer cũ không sửa gì code, có còn chạy đúng không?" Nếu chắc chắn "có" → additive. Nếu "không" hoặc "không chắc" → coi như breaking.
- Nếu là additive — ưu tiên đưa vào version hiện tại. Không cần tạo version mới. Chỉ cần đảm bảo field mới là optional, parameter mới có default, và document rõ ràng. Đây là con đường rẻ nhất, nhanh nhất.
- Nếu là breaking — cân nhắc có thể "biến" nó thành additive không. Đây là tư duy quan trọng nhất của BA giỏi. Thay vì đổi tên field, hãy thêm field mới song song. Thay vì xóa endpoint, hãy thêm endpoint mới và để cái cũ sống thêm một thời gian. Rất nhiều breaking change có thể "né" được bằng cách thiết kế additive khéo léo.
- Nếu bắt buộc breaking — lập kế hoạch versioning. Chọn
/v2(URI versioning thường là lựa chọn an toàn nhất cho dễ hiểu). Spec phải nêu:/v1và/v2cùng tồn tại bao lâu, ai chịu trách nhiệm maintain cả hai.
- Soạn kế hoạch deprecation. Thông báo cho consumer qua bao nhiêu kênh (email, portal, changelog), thời gian chuyển đổi bao lâu, dùng header cảnh báo (
Deprecation,Sunset) ra sao, và cách đo lường còn ai dùng version cũ.
- Viết vào tài liệu và changelog. Mọi thay đổi — kể cả additive — đều phải có trong changelog để consumer biết. Đây là phần BA tuyệt đối không được bỏ qua.
Lỗi thường gặp & mẹo
Lỗi 1 — Tưởng đổi tên field là chuyện nhỏ. "Chỉ đổi cái tên thôi mà." Không. Đổi tên là breaking 100%. Mẹo: trong mọi buổi review spec, có một field nào đổi tên là phải bật đèn đỏ.
Lỗi 2 — Bỏ qua thay đổi ngữ nghĩa (semantic). Cấu trúc JSON y hệt nhưng ý nghĩa của giá trị đổi (như ví dụ status = "completed") là loại breaking nguy hiểm nhất vì không công cụ nào tự bắt được, test contract vẫn pass. Mẹo: khi review, đừng chỉ nhìn schema, hãy hỏi "ý nghĩa của field này có thay đổi không?"
Lỗi 3 — Tạo version mới cho mọi thay đổi. Ngược lại với lỗi 1, có người thấy thay đổi gì cũng nhảy lên /v2, /v3, /v4... Maintain nhiều version song song cực kỳ tốn kém. Mẹo: chỉ tạo version mới khi thật sự là breaking và không "né" được. Additive thì giữ nguyên version.
Lỗi 4 — Xóa version cũ mà không thông báo và không đo lường. "Chắc không ai dùng /v1 nữa đâu." Đừng đoán — hãy đo. Mẹo: trước khi tắt version, dựng dashboard đếm lưu lượng. Nếu vẫn còn traffic, còn người dùng.
Lỗi 5 — Quên header default cho parameter mới. Thêm parameter request mà không có giá trị mặc định = breaking ngầm. Mẹo: mọi parameter mới đều phải có default an toàn nếu không truyền.
Mẹo vàng: Hãy coi API như một lời hứa. Bạn có thể hứa thêm (additive) thoải mái, nhưng rút lại lời hứa cũ (breaking) thì luôn phải báo trước và cho thời gian. Tư duy này giúp BA ra quyết định đúng trong 90% tình huống.
Bài tập thực hành
Bài 1 — Phân loại thay đổi. Với mỗi thay đổi sau, hãy xác định additive hay breaking, và giải thích ngắn gọn:
a) Thêm field discount_code (optional) vào response đơn hàng.
b) Đổi field phone từ "0901234567" thành object { "country_code": "+84", "number": "901234567" }.
c) Thêm endpoint GET /v1/products/featured.
d) Đổi created_date từ format dd/MM/yyyy sang ISO yyyy-MM-ddTHH:mm:ssZ.
e) Thêm parameter optional sort_by với default là created_at.
Bài 2 — Biến breaking thành additive. API hiện trả full_name. Business muốn tách thành first_name và last_name. Hãy đề xuất cách thay đổi sao cho không gãy consumer cũ, và mô tả lộ trình deprecation nếu cuối cùng muốn bỏ full_name.
Bài 3 — Soạn mini kế hoạch deprecation. Công ty bạn có /v1/orders đang được 15 đối tác dùng. Bạn cần ra mắt /v2 với cấu trúc mới và khai tử /v1. Hãy viết một đoạn ngắn (5–7 dòng) nêu: cách thông báo, thời gian chuyển đổi, header cảnh báo sẽ dùng, và cách bạn đo lường còn ai dùng /v1.
Gợi ý đáp án Bài 1: a) additive; b) breaking (đổi type); c) additive; d) breaking (đổi format); e) additive.
Tóm tắt
- API là một hợp đồng giữa các hệ thống. Phá vỡ hợp đồng mà không báo trước sẽ làm gãy mọi consumer đang phụ thuộc.
- Additive (tương thích ngược): thêm field optional, thêm endpoint mới, thêm parameter có default, nới lỏng validation. Đây là hướng an toàn, không cần version mới.
- Breaking: đổi tên field, đổi kiểu/format dữ liệu, xóa field/endpoint, thêm field bắt buộc, siết validation, và đặc biệt là đổi ngữ nghĩa (nguy hiểm nhất vì test khó bắt).
- Tư duy quan trọng nhất của BA: luôn cố biến breaking thành additive bằng cách thêm song song thay vì sửa/xóa.
- Khi buộc phải breaking, dùng versioning (
/v1,/v2— URI versioning dễ hiểu nhất) cho hai bên cùng tồn tại, kèm kế hoạch deprecation có kiểm soát: thông báo nhiều kênh, lộ trình 6–12 tháng, headerDeprecation/Sunset, và đo lường lưu lượng trước khi tắt. - BA là người gác cổng: phân loại thay đổi, chặn breaking change ẩn trong release thường, và soạn lộ trình chuyển đổi để cả công ty lẫn đối tác cùng an toàn.