Phát hành
Backward compatibility giữa app và backend
User không update app ngay. Mọi thay đổi backend phải sống chung với các app version cũ còn ngoài thị trường, hoặc phải có cách buộc update.
Nội dung bài
Thuật ngữ trong bài (13)
- build · bản dựng
- CI · tích hợp liên tục
- deprecated · đánh dấu ngừng dùng
- feature flag · công tắc tính năng
- release · bản phát hành
- rollback · quay về bản cũ
- API · giao diện lập trình
- backend · phần máy chủ
- backward compatibility · tương thích ngược
- endpoint · điểm gọi
- retry · thử lại
- schema · hình dạng dữ liệu
- timeout · hạn chờ
Vấn đề riêng của mobile
Trên web, deploy xong là mọi user dùng bản mới trong vài phút. Trên mobile thì không: sau khi release, thị trường có nhiều app version chạy song song trong nhiều tháng. Một phần user không bao giờ update cho tới khi buộc phải làm.
Hệ quả: backend không phục vụ một client. Nó phục vụ tất cả app version còn sống. Một thay đổi backend tưởng như vô hại có thể giết chết các bản cũ ngay lập tức, và những user đó thậm chí không biết vì sao app đột nhiên hỏng.
Version matrix
Dự án giữ bảng này và cập nhật mỗi release:
| App version | API version | Trạng thái | Ghi chú |
|---|---|---|---|
| 1.0 – 1.2 | v1 | Deprecated | Hết hỗ trợ từ ngày công bố |
| 1.3 – 1.5 | v1 | Minimum supported | Còn phục vụ, hiện banner mời update |
| 1.6 | v1, v2 | Recommended | Bản đa số user đang dùng |
| 1.7 | v2 | Latest | Bản mới nhất trên store |
Ba mốc phải luôn trả lời được, và backend phải đọc được chúng từ cấu hình, không phải từ trí nhớ của một người:
- Minimum supported version — dưới mức này app bị chặn, buộc update.
- Recommended version — dưới mức này hiện thông báo mời update, không chặn.
- Latest version — bản đang có trên store.
Luật thay đổi API
Trong cùng một API version, chỉ được phép thay đổi theo hướng cộng thêm:
| Được phép | Không được phép |
|---|---|
| Thêm field mới vào response | Đổi tên field đang có |
| Thêm endpoint mới | Xoá field đang có |
| Thêm giá trị mới vào enum, nếu client đã xử lý giá trị lạ | Đổi kiểu dữ liệu của field |
| Nới lỏng validation đầu vào | Siết chặt validation đầu vào |
| Đổi ý nghĩa của một field mà giữ nguyên tên |
Ví dụ kinh điển: backend đổi userName thành username. Với backend đó là một dòng diff. Với
các app version cũ đang parse userName, đó là màn hình trắng hoặc crash cho toàn bộ user chưa
update.
Cần thay đổi phá vỡ tương thích thì mở API version mới, giữ version cũ chạy song song cho tới khi số user ở các app version cũ rơi xuống dưới ngưỡng đã công bố.
Contract test
Luật ở trên chỉ có giá trị nếu có cơ chế chặn tự động. Cơ chế đó là contract test: schema của mỗi response được publish và version hoá; CI của backend chạy kiểm tra schema mới so với schema các version còn được hỗ trợ, và fail build khi phát hiện thay đổi phá vỡ tương thích.
Không có contract test thì luật này chỉ tồn tại trên giấy, và nó sẽ bị vi phạm bởi một người không biết luật, vào một chiều thứ sáu.
Force upgrade
Backend trả về cho client biết version tối thiểu; app so sánh và chặn nếu thấp hơn.
Force upgrade là phương án cuối, không phải công cụ tiện tay. Nó đẩy toàn bộ chi phí sang user: họ đang giữa việc thì bị chặn, phải tải một bản cập nhật có thể nặng, có thể đang ở mạng di động. Chỉ dùng khi:
- Có lỗ hổng bảo mật ở bản cũ, hoặc
- Bản cũ tạo ra dữ liệu sai mà không sửa được từ phía server, hoặc
- API version cũ đã được thông báo deprecated đủ lâu và số user còn lại rất nhỏ.
Không dùng force upgrade chỉ vì backend muốn dọn code cho gọn.
Màn hình chặn phải nói rõ vì sao và có nút mở thẳng store. Chặn mà không giải thích là cách nhanh nhất để nhận đánh giá một sao.
Feature flag và remote config
Trên mobile không rollback được binary đã cài. Vì vậy khả năng tắt một tính năng từ xa là thứ thay thế cho rollback — xem Quy trình release và rollback.
| Nên điều khiển từ xa | Không nên |
|---|---|
| Bật/tắt một tính năng mới | Toàn bộ business logic |
| Tỷ lệ user được mở tính năng | Công thức tính tiền, thuế, chiết khấu |
| Timeout, số lần retry | Quy tắc phân quyền |
| Minimum supported version | Nội dung màn hình phức tạp |
| Chế độ bảo trì, URL hỗ trợ |
Hai luật đi kèm:
- Mỗi flag có chủ và có hạn. Flag sống quá hai release mà không được dọn thì trở thành nợ: số nhánh code nhân đôi mỗi lần thêm một flag, và không ai còn dám xoá.
- App phải chạy đúng khi không lấy được remote config. Luôn có giá trị mặc định biên dịch sẵn trong app. Config server chết mà app chết theo là đã tự tạo thêm một điểm gãy.
Giới hạn
- Backward compatibility tốn chi phí thật: backend phải giữ code cũ, test phải chạy trên nhiều version. Vì vậy phải có chính sách deprecate rõ ràng, nếu không số version phải hỗ trợ sẽ tăng mãi.
- Version matrix chỉ hữu ích khi được cập nhật. Bảng cũ sáu tháng còn nguy hiểm hơn không có bảng, vì người ta vẫn tin vào nó.

