Chuyển đến nội dung
MINH VO Ghi chép từ công việc
của một kỹ sư tại Việt Nam
Cơ bản10 phút đọc

Phương thức HTTP cho API đơn hàng: GET, POST, PUT và DELETE cam kết điều gì?

Chọn phương thức HTTP theo tác động của thao tác, rồi xác định rõ cách thay thế dữ liệu, gửi lại yêu cầu và xóa đơn hàng bằng các ví dụ cụ thể.

Các thành phần máy chủ màu ô liu và than chì nối với nhau trên nền be ấm

Phương thức HTTP cho phía gọi API biết một phần ý nghĩa của yêu cầu, ngay cả khi chưa hiểu nghiệp vụ của ứng dụng. GET yêu cầu một biểu diễn của tài nguyên. POST yêu cầu tài nguyên xử lý nội dung được gửi tới. PUT tạo mới hoặc thay thế trạng thái của tài nguyên đích. DELETE yêu cầu gỡ bỏ mối liên hệ giữa URI đích và chức năng hiện tại của nó. Khi xây dựng API đơn hàng, các khác biệt này giúp bạn xác định thao tác nào có thể được gửi lại và thao tác nào cần thêm quy ước ở tầng ứng dụng.

Phương thức không mô tả mọi việc diễn ra bên trong máy chủ. GET vẫn có thể tạo một dòng nhật ký; DELETE vẫn có thể giữ lại bản ghi để kiểm toán. RFC 9110 định nghĩa ngữ nghĩa dựa trên tác động mà phía gọi yêu cầu, đồng thời phân biệt tính an toàn và tính lũy đẳng. Đó là cơ sở để thiết kế endpoint trước khi chọn tên hàm xử lý.

Xác định tài nguyên đích của thao tác

Giả sử một cửa hàng có đơn hàng và tùy chọn giao hàng. Các yêu cầu dưới đây thể hiện ba thao tác khác nhau:

GET /orders/418 HTTP/1.1
Host: shop.example
Accept: application/json

POST /orders HTTP/1.1
Host: shop.example
Content-Type: application/json

{"items":[{"sku":"pen-blue","quantity":2}]}

PUT /customers/73/delivery-preferences HTTP/1.1
Host: shop.example
Content-Type: application/json

{"leaveAtDoor":false,"instructions":"Ring the bell"}

GET lấy biểu diễn của một đơn hàng. POST gửi yêu cầu tới tập hợp đơn hàng, để máy chủ chọn mã cho đơn mới. PUT chỉ rõ tài nguyên tùy chọn giao hàng mà phía gọi muốn thay thế. Nếu phía gọi tự cung cấp mã đơn hàng ổn định, PUT cũng có thể phù hợp để tạo đơn, với điều kiện máy chủ thực sự triển khai ngữ nghĩa thay thế của phương thức này.

Tên tài nguyên không tự bảo đảm hợp đồng API đúng. Một route mang tên /orders vẫn gây hiểu nhầm nếu hàm xử lý GET thực hiện thu tiền. Thanh toán là thay đổi trạng thái có chủ đích, nên đặt thao tác đó sau GET mâu thuẫn với tính an toàn của phương thức. Trình duyệt, trình thu thập dữ liệu và các phần mềm khác có thể gửi yêu cầu an toàn mà không xin người dùng xác nhận riêng cho một thay đổi trạng thái.

An toàn và lũy đẳng trả lời hai câu hỏi khác nhau

Một phương thức an toàn có ngữ nghĩa yêu cầu về cơ bản là chỉ đọc. Một phương thức lũy đẳng có tác động dự kiến sau nhiều yêu cầu giống hệt nhau tương đương với tác động sau một yêu cầu. GET vừa an toàn vừa lũy đẳng. PUT và DELETE lũy đẳng nhưng được dùng để thay đổi trạng thái. POST không có bảo đảm lũy đẳng chung.

Giả sử phía gọi gửi lại PUT cập nhật tùy chọn giao hàng vì kết nối đã đóng trước khi nhận phản hồi. Đặt lại cùng một bộ tùy chọn phải để tài nguyên ở cùng trạng thái dự kiến. Máy chủ có thể ghi nhật ký cả hai lần. Mã trạng thái phản hồi cũng có thể khác nhau: lần đầu tạo tài nguyên, lần sau cập nhật tài nguyên đã tồn tại. Chỉ riêng những khác biệt đó không làm mất tính lũy đẳng.

Gửi lại POST tạo đơn có thể tạo hai đơn hàng nếu API không có cơ chế loại bỏ yêu cầu trùng. Tên phương thức không cho biết lần gọi đầu đã ghi nhận thay đổi hay chưa. Để phục hồi khi chưa biết kết quả, API có thể nhận một mã lệnh ổn định ở tầng ứng dụng và trả lại kết quả đã lưu khi lệnh được gửi lại. Mã này cần được xử lý bền vững; một bảng tra cứu chỉ nằm trong bộ nhớ sẽ mất khi tiến trình khởi động lại.

Các yêu cầu trỏ tới tài nguyên và tác động tương ứng; PUT lặp lại giữ cùng một trạng thái tùy chọn giao hàngMở ảnh kích thước đầy đủ ↗

Hai lần PUT trong hình cùng trỏ tới một tài nguyên và cùng gửi một giá trị thay thế. Ô POST biểu diễn một lần gửi mới, trừ khi ứng dụng chủ động liên kết các lần thử bằng cùng một mã lệnh.

Thay thế dữ liệu cần một hợp đồng ghi đầy đủ

Nội dung PUT không nhất thiết chứa mọi trường mà GET trả về. Máy chủ có thể tự sinh mã đơn, thời điểm tạo hoặc tổng tiền tính toán. Hợp đồng phải nêu rõ biểu diễn nào thay thế phần trạng thái do phía gọi kiểm soát, và trường nào thuộc quyền quản lý của máy chủ.

Với tùy chọn giao hàng, hãy quyết định điều gì xảy ra khi thiếu instructions. Nếu thiếu trường này đồng nghĩa với xóa hướng dẫn cũ, hãy ghi rõ. Nếu thiếu nghĩa là giữ nguyên hướng dẫn, thao tác đang có hành vi cập nhật một phần và cần được giải thích cẩn thận hoặc dùng phương thức khác. Một interface TypeScript đánh dấu trường là tùy chọn không đủ để phía gọi suy ra quy tắc đó.

Kiểm tra dữ liệu đầu vào cũng thuộc hợp đồng. Máy chủ nên từ chối đầu vào không hợp lệ thay vì âm thầm thực hiện một thay đổi khác về ý nghĩa. Giá trị leaveAtDoor: "sometimes" không phải boolean như biểu diễn ở trên. Hãy trả về lỗi phía gọi đã được mô tả, giữ nguyên trạng thái hợp lệ trước đó và chỉ ra trường bị lỗi.

OpenAPI 3.1.1 có thể mô tả thao tác, schema đầu vào và cấu trúc phản hồi. Tài liệu giúp phía gọi tìm hiểu hợp đồng; kiểm thử triển khai vẫn cần xác nhận tác động thật lên dữ liệu đã lưu.

Xóa tài nguyên là một quyết định nghiệp vụ

Với DELETE /orders/418, trước hết cần xác định có được phép xóa đơn hàng hay không. Đơn đã thanh toán có thể cần một quy trình hủy giữ lại lịch sử tài chính và phát lệnh hoàn tiền. Gọi thao tác đó là DELETE không làm quy trình đơn giản hơn. Một tài nguyên hủy riêng có thể biểu diễn tiến độ nếu hành động nghiệp vụ gồm nhiều bước.

DELETE thành công cũng không nhất thiết có nghĩa là mọi bản ghi đã bị xóa vật lý ngay lập tức. RFC 9110 mô tả việc gỡ bỏ mối liên hệ tại URI đích. Chính sách lưu giữ, nhật ký kiểm toán và nghĩa vụ bảo vệ dữ liệu cần có đặc tả riêng. Đừng cam kết xóa dữ liệu không thể phục hồi nếu route thực tế chỉ đổi một cờ trạng thái.

Nếu lần DELETE đầu thành công và lần gửi lại nhận 404, trạng thái cuối cùng vẫn có thể giống nhau: tài nguyên đích không còn khả dụng. Hãy quyết định thư viện phía gọi sẽ coi phản hồi thứ hai là hoàn tất việc xóa hay là lỗi cần xử lý.

Thiết kế việc gửi lại từ tình huống mất phản hồi

Một diễn tiến lỗi hữu ích phải tách việc máy chủ ghi nhận thay đổi khỏi việc phía gọi nhìn thấy kết quả. Giả sử POST /orders tạo đơn 418, nhưng kết nối đóng trước khi phản hồi 201 tới nơi. Với phía gọi, tình huống này có thể giống hệt việc yêu cầu chưa bao giờ tới máy chủ. Gửi POST thông thường lần nữa có thể tạo đơn 419. Cả hai lần, máy chủ đều làm đúng hợp đồng tạo mới; phần còn thiếu là cách nhận diện cùng một lệnh qua nhiều lần thử.

Bài viết của đội ngũ kỹ thuật Stripe về tính lũy đẳng phân tích tình huống này và lý do cần thiết kế khả năng gửi lại an toàn ngay trong API. Cách tiếp cận đó là một hợp đồng ứng dụng bổ sung trên HTTP. Khóa lệnh nối các lần thử với nhau; dịch vụ lưu đủ kết quả ban đầu để trả lời nhất quán khi nhận lại lệnh.

Đơn hàng được ghi nhận nhưng phản hồi bị mất; một lệnh mới có thể tạo thêm đơn, còn việc gửi lại cùng mã lệnh đã lưu bền vững trả về đơn ban đầuMở ảnh kích thước đầy đủ ↗

Với dịch vụ đơn hàng, hãy xác định khi nào hai yêu cầu là cùng một lệnh. Giới hạn phạm vi khóa theo tài khoản hoặc tenant đã xác thực, gắn khóa với tham số đầu vào đã chấp nhận và từ chối việc dùng lại khóa với nội dung không tương thích. Nếu chỉ lưu khóa bằng một lần ghi riêng sau khi đơn đã được ghi nhận, sự cố ở giữa hai lần ghi vẫn có thể gây trùng đơn. Tác động lên đơn hàng và bản ghi phục vụ gửi lại cần chung một ranh giới nguyên tử, hoặc cần một thiết kế phục hồi có thể dựng lại liên hệ bị thiếu.

PUT cũng cần quy tắc xử lý chỉnh sửa đồng thời. Giả sử tab A đặt hướng dẫn thành “Ring the bell”, mất phản hồi và chờ trước khi thử lại. Tab B sau đó đổi hướng dẫn thành “Leave with reception”. Một PUT không kèm điều kiện từ tab A có thể ghi đè lựa chọn mới của tab B. Tính lũy đẳng mô tả tác động dự kiến khi lặp lại thao tác; nó không ngăn người khác ghi vào tài nguyên trong thời gian chờ.

Hãy dùng điều kiện trước gắn với phiên bản biểu diễn khi phía gọi chỉ muốn sửa đúng phiên bản đã đọc. Khi If-Match không thỏa mãn, phía gọi cần xem trạng thái hiện tại trước khi gửi bản thay thế dựa trên thông tin mới. Đừng tự gắn một giá trị kiểm tra phiên bản mới vào nội dung cũ rồi coi đó là giải quyết xung đột. Người dùng chưa đồng ý ghi đè thay đổi xuất hiện trong lúc chờ.

Kiểm thử đặc tính mà phương thức cam kết

Một kiểm thử PUT hữu ích bắt đầu từ tùy chọn đã tồn tại, gửi cùng bản thay thế hợp lệ hai lần rồi so sánh phần trạng thái do phía gọi kiểm soát sau mỗi lần. Một kiểm thử GET hữu ích xác nhận rằng việc đọc đơn không kích hoạt thêm bước chuyển trạng thái của đơn. Những kiểm thử này xem xét hành vi mà chỉ chụp lại phản hồi không thể chứng minh.

Tách chính sách gửi lại khỏi việc chọn phương thức. Tính lũy đẳng giúp phía gọi suy luận về yêu cầu lặp lại, nhưng thông tin xác thực, header điều kiện và thay đổi tài nguyên vẫn có thể ảnh hưởng kết quả. Mô tả điều kiện được hỗ trợ và phản hồi lỗi bên cạnh phương thức để phía gọi phân biệt một lần thử lại cùng thao tác với một thao tác mới.

Nguồn tham khảo & đọc thêm

  1. RFC 9110, ngữ nghĩa HTTP
  2. OpenAPI specification 3.1.1
  3. Stripe engineering, designing APIs with idempotency
← Về trang bài viết

Hình minh họa

100%