1 điểm bởi GN⁺ 4 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Khi một API web công khai dùng đồng thời tên như Product API và đường dẫn /api/v1, phiên bản ngữ nghĩa và cấu trúc của chính API có thể bị lệch nhau
  • Nếu dùng song song đường dẫn /v1/major.minor.patch, route và hợp đồng API sẽ bị trộn lẫn, đồng thời chữ số đầu của phiên bản ngữ nghĩa bị cố định trong URL
  • Với các thay đổi phá vỡ tính tương thích, sẽ cần đường dẫn mới và các route reverse proxy, khiến thông tin hợp đồng có thể bị phân tán giữa URL và số phiên bản
  • Nếu đồng thời tạo các API kế tiếp, API hiện tại trên thực tế sẽ bị trói vào v1, và về sau khi có thay đổi phá vỡ tương thích thì ý nghĩa của tên và đường dẫn trở nên mơ hồ
  • Đây là vấn đề xuất phát từ mong muốn tìm ra những cách quản lý phiên bản API web công khai thường gây khó chịu lặp đi lặp lại, cùng các nguyên tắc thiết kế tốt hơn

1 bình luận

 
Ý kiến trên Lobste.rs
  • Việc đặt /v1/ trong URL thực ra có một lợi thế lớn: nó buộc bạn không làm hỏng API đối với người dùng cho đến khi bạn tắt endpoint đó

  • Evolving HTTP APIs và các bài viết khác của cùng tác giả đưa ra nhiều lời khuyên hữu ích

  • Về cơ bản, tôi gắn /v1/, /v2/ vào từng route để biểu thị các thay đổi phá vỡ tính tương thích. Nếu đây là một API công khai đang vận hành chứ không phải nỗ lực định nghĩa một tiêu chuẩn chạy trên nhiều host, thì chẳng có nhiều lý do để dùng semantic versioning đầy đủ
    Semantic versioning tồn tại để các nhà phát triển khác có thể tự tin nâng cấp dependency mà không phải mất 20 phút đọc changelog, nhưng với API đang chạy thì mọi người không thể chọn thời điểm nhận bản minor mới hay bản vá lỗi mới
    Điều được xem là thay đổi phá vỡ tương thích là khi bạn thay đổi hành vi đã được tài liệu hóa, hoặc làm hỏng các client hiện có đang phụ thuộc vào hành vi đã được tài liệu hóa đó. Có nơi còn coi cả thay đổi trong hành vi chưa được tài liệu hóa là phá vỡ, nhưng cách đó tiềm ẩn nhiều rủi ro

  • Ở Google họ làm như sau: AIP-185: API Versioning, AIP-180: Bacwards compatibility
    Tôi thấy các tài liệu thiết kế này khá đặc thù với cách Google làm việc, nhưng khi thiết kế API tôi vẫn thường tham khảo, và một số ý tưởng trong đó thực sự rất hữu ích

  • Nói chung, tôi cho rằng mọi API nên cố gắng giảm tối đa các thay đổi phá vỡ tính tương thích. Ví dụ, nếu muốn đổi tên một thuộc tính thì tốt hơn nên thêm trùng thuộc tính với tên mới thay vì xóa thuộc tính cũ
    Tuy vậy, cách the people at Buttondown do it cũng khá gọn gàng. Họ định nghĩa các bước migrate giữa các phiên bản API, để bên tiêu thụ có thể ghim phiên bản API của mình bằng header còn bên cung cấp vẫn tiếp tục thay đổi được

    • Với các thuộc tính đầu ra, cách nhân đôi thuộc tính hoạt động khá ổn. Nhưng ở đầu vào thì bạn phải xử lý trường hợp client gửi hai thuộc tính với hai giá trị khác nhau
      Câu trả lời kiểu “tên mới luôn được ưu tiên” nghe có vẻ hợp lý, nhưng nó có thể đổ vỡ nếu client thực hiện chu trình đọc-sửa-ghi rồi gửi lại bản sửa đổi của đối tượng do server tạo ra. Client có thể chỉ cập nhật thuộc tính cũ và gửi trả lại nguyên trạng thuộc tính mới mà nó đã bỏ qua
    • Việc cung cấp migration giữa các phiên bản API như nhà cung cấp API mô tả nghe có vẻ hay, nhưng dùng HTTP request header để versioning có thể gây ra vấn đề
    • Liên kết đó giải thích rất hay cách xử lý hình dạng dữ liệu. Tuy nhiên đó mới chỉ là một phần, và tôi tò mò không biết nên làm gì khi chính hành vi thay đổi
      Có vẻ những phép biến đổi kiểu đó cũng có thể áp dụng cho việc gán hành vi, nhưng nếu tôi không bỏ sót gì thì phần đó không được đề cập
  • Lý tưởng nhất là đưa phiên bản vào đường dẫn và khiến các phiên bản mới có tính bổ sung. Khi đó API phiên bản cũ có thể định tuyến lại request sang phiên bản API mới hơn sau khi thực hiện các phép biến đổi đầu vào/đầu ra cần thiết
    Vài năm sau, khi không còn ai dùng một phiên bản cũ nào đó nữa, bạn có thể xóa nó đi và đường dẫn /v1/ sẽ trả lỗi

  • Trước đây tôi có đọc một chút về cách versioning API bằng content negotiation qua header Accept. Nếu ai đã từng versioning API theo cách đó, tôi rất muốn nghe trải nghiệm
    Theo kinh nghiệm của tôi, versioning theo từng resource hoặc versioning toàn cục là những cách tiếp cận trực quan nhất. Với việc ngừng hỗ trợ, kết hợp dùng header phản hồi HTTP Deprecation (RFC 9745) và cuối cùng trả về phản hồi như 410 Gone cho endpoint cũ có vẻ là cách hợp lý để thúc đẩy client chuyển sang phiên bản mới
    Ngoài ra, tôi cũng thật sự tò mò không biết có ai từng xây dựng một API có thể tiến hóa hay chưa. Ý tôi là kiểu hệ thống nội bộ dịch request của phiên bản cũ sang request của phiên bản API mới, rồi sau khi client đã chuyển đi hoặc sau một khoảng thời gian thì thực sự xóa phiên bản cũ