Tôi nên sử dụng PATCH hoặc PUT trong API REST?


274

Tôi muốn thiết kế điểm cuối nghỉ ngơi của mình với phương thức thích hợp cho kịch bản sau.

Có một nhóm. Mỗi nhóm có một trạng thái. Nhóm có thể được kích hoạt hoặc bất hoạt bởi quản trị viên.

Tôi có nên thiết kế điểm cuối của mình là

PUT /groups/api/v1/groups/{group id}/status/activate

HOẶC LÀ

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}

1
Cả hai đều ổn. Nhưng hãy xem RFC cho định dạng JSON PATCH ( tools.ietf.org/html/rfc6902 ). PATCH hy vọng sẽ nhận được một số loại tài liệu diff / patch cho tải trọng (và JSON thô không phải là một trong số chúng).
Jørn Wildt

1
@ JørnWildt không, PUT sẽ là một lựa chọn khủng khiếp. Bạn đang đặt gì ở đó? PATCH là lựa chọn hợp lý duy nhất. Chà, trong trường hợp này, bạn có thể sử dụng định dạng PATCH được trình bày trong câu hỏi và chỉ sử dụng phương pháp PUT; ví dụ PUT là sai.
thecoshman

3
Không có gì sai khi phơi bày một hoặc nhiều thuộc tính dưới dạng tài nguyên độc lập mà khách hàng có thể NHẬN và sửa đổi bằng PUT. Nhưng, vâng, URL sau đó phải là / Groups / api / v1 / Groups / {group id} / status mà bạn có thể PUT "kích hoạt" hoặc "không hoạt động" hoặc GET để đọc trạng thái hiện tại.
Jørn Wildt

3
Dưới đây là một lời giải thích tốt về cách PATCH thực sự nên được sử dụng: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat

4
" activate" là không đầy đủ xây dựng RESTful. Có lẽ bạn đang cố gắng cập nhật statusthành "hoạt động" hoặc "không hoạt động". trong trường hợp đó, bạn có thể BẮT ĐẦU .../statusvới chuỗi "hoạt động" hoặc "không hoạt động" trong cơ thể. Hoặc nếu bạn đang cố cập nhật boolean tại status.active, bạn có thể BẮT ĐẦU .../status/activevới boolean trong cơ thể
Augie Gardner

Câu trả lời:


327

Các PATCHphương pháp là sự lựa chọn đúng ở đây là bạn đang cập nhật một nguồn tài nguyên hiện có - nhóm ID. PUTchỉ nên được sử dụng nếu bạn thay thế toàn bộ tài nguyên.

Thông tin thêm về sửa đổi tài nguyên một phần có sẵn trong RFC 5789 . Cụ thể, PUTphương pháp được mô tả như sau:

Một số ứng dụng mở rộng Giao thức truyền siêu văn bản (HTTP) yêu cầu một tính năng để thực hiện sửa đổi tài nguyên một phần. Phương thức HTTP PUT hiện tại chỉ cho phép thay thế hoàn toàn một tài liệu. Đề xuất này thêm một phương thức HTTP mới, PATCH, để sửa đổi tài nguyên HTTP hiện có.


1
Để công bằng, bạn có thể PUT chuỗi 'kích hoạt' hoặc 'hủy kích hoạt' cho tài nguyên. Vì dường như chỉ có một thứ để chuyển đổi, thay thế hoàn toàn nó không phải là một vấn đề quá lớn. Và nó cho phép một yêu cầu nhỏ hơn (không đáng kể).
thecoshman

35
Điều quan trọng cần lưu ý là RFC 5789 vẫn đang trong giai đoạn đề xuất và chưa được chấp nhận chính thức và hiện được gắn cờ là "tồn tại bất ổn". 'Cách thực hành tốt nhất' này đang được tranh luận rất nhiều và về mặt kỹ thuật, PATCH chưa phải là một phần của tiêu chuẩn HTTP.
fishpen0

4
Chỉ 2 xu của tôi vài năm sau: bạn có thể coi chính trạng thái là tài nguyên và nếu vậy, sử dụng PUT chống lại / trạng thái về mặt kỹ thuật sẽ thay thế tài nguyên trạng thái ở điểm cuối đó.
Jono Stewart

3
Tôi sẽ dám tranh luận chống lại các tài liệu, mặc dù đó là "RFC". Các tài liệu nói rằng bạn nên sử dụng PATCH để sửa đổi chỉ là một phần của tài nguyên, nhưng nó đã bỏ qua điều quan trọng là phương thức PATCH được định nghĩa là phương thức không phải là idempotent. Tại sao? Nếu phương thức PUT được tạo ra với mục đích cập nhật / thay thế toàn bộ tài nguyên, thì tại sao phương thức PATCH không được tạo ra như một phương thức tạm thời như PUT, nếu mục đích của nó chỉ là cập nhật một phần của tài nguyên? Đối với tôi, có vẻ như có nhiều sự khác biệt về tính không thay đổi của cập nhật, như "a = 5" (PUT) và "a = a + 5" (PATCH). Cả hai đều có thể cập nhật toàn bộ tài nguyên.
Mladen B.

179

Các R trong REST là viết tắt của tài nguyên

(Điều đó không đúng, vì nó là viết tắt của Đại diện, nhưng đó là một mẹo hay để ghi nhớ tầm quan trọng của Tài nguyên trong REST).

Giới thiệu PUT /groups/api/v1/groups/{group id}/status/activate: bạn không cập nhật "kích hoạt". "Kích hoạt" không phải là một thứ, nó là một động từ. Động từ không bao giờ là tài nguyên tốt. Một nguyên tắc nhỏ: nếu hành động, một động từ, nằm trong URL, thì có lẽ nó không phải là RESTful .

Bạn đang làm gì thay thế? Hoặc bạn đang "thêm", "xóa" hoặc "cập nhật" kích hoạt trên Nhóm hoặc nếu bạn muốn: thao tác với "trạng thái" - cung cấp nguồn trên Nhóm. Cá nhân, tôi sử dụng "kích hoạt" vì chúng ít mơ hồ hơn khái niệm "trạng thái": tạo trạng thái không rõ ràng, tạo kích hoạt thì không.

  • POST /groups/{group id}/activation Tạo (hoặc yêu cầu tạo) một kích hoạt.
  • PATCH /groups/{group id}/activationCập nhật một số chi tiết của một kích hoạt hiện có. Vì một nhóm chỉ có một kích hoạt, chúng tôi biết tài nguyên kích hoạt nào chúng tôi đang đề cập đến.
  • PUT /groups/{group id}/activationChèn hoặc thay thế kích hoạt cũ. Vì một nhóm chỉ có một kích hoạt, chúng tôi biết tài nguyên kích hoạt nào chúng tôi đang đề cập đến.
  • DELETE /groups/{group id}/activation Sẽ hủy hoặc xóa kích hoạt.

Mẫu này hữu ích khi "kích hoạt" của Nhóm có tác dụng phụ, chẳng hạn như thanh toán được thực hiện, thư được gửi, v.v. Chỉ POST và PATCH có thể có tác dụng phụ như vậy. Khi ví dụ xóa một kích hoạt cần phải nói, thông báo cho người dùng qua thư, XÓA không phải là lựa chọn đúng đắn; trong trường hợp đó, bạn có thể muốn tạo tài nguyên hủy kích hoạt : POST /groups/{group_id}/deactivation.

Bạn nên tuân theo các hướng dẫn này, vì hợp đồng tiêu chuẩn này cho thấy rõ ràng cho khách hàng của bạn và tất cả các proxy và lớp giữa khách hàng và bạn, biết khi nào an toàn để thử lại và khi nào không. Giả sử máy khách đang ở một nơi nào đó có wifi không ổn định và người dùng của nó nhấp vào "hủy kích hoạt", điều này sẽ kích hoạt DELETE: Nếu thất bại, khách hàng có thể thử lại, cho đến khi nhận được 404, 200 hoặc bất cứ điều gì khác mà nó có thể xử lý. Nhưng nếu nó kích hoạt POST to deactivationthì nó biết không thử lại: POST ngụ ý điều này.
Bất kỳ khách hàng nào hiện có hợp đồng, khi được theo dõi sẽ bảo vệ khỏi việc gửi 42 email "nhóm của bạn đã bị hủy kích hoạt", đơn giản vì thư viện HTTP của nó tiếp tục thử lại cuộc gọi đến phụ trợ.

Cập nhật một thuộc tính duy nhất: sử dụng VÒI

PATCH /groups/{group id}

Trong trường hợp bạn muốn cập nhật một thuộc tính. Ví dụ: "trạng thái" có thể là một thuộc tính trên Nhóm có thể được đặt. Một thuộc tính như "trạng thái" thường là một ứng cử viên tốt để giới hạn trong danh sách trắng các giá trị. Các ví dụ sử dụng một số lược đồ JSON không xác định:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

Thay thế tài nguyên, không có tác dụng phụ sử dụng PUT.

PUT /groups/{group id}

Trong trường hợp bạn muốn thay thế toàn bộ Nhóm. Điều này không nhất thiết có nghĩa là máy chủ thực sự tạo ra một nhóm mới và ném nhóm cũ ra ngoài, ví dụ: các id có thể vẫn giữ nguyên. Nhưng đối với các khách hàng, đây là những gì PUT có thể có nghĩa là: khách hàng nên cho anh nhận được một mục hoàn toàn mới, dựa trên phản ứng của máy chủ.

Trong trường hợp PUTyêu cầu, khách hàng phải luôn gửi toàn bộ tài nguyên, có tất cả dữ liệu cần thiết để tạo một mục mới: thường là cùng một dữ liệu mà POST-tạo sẽ yêu cầu.

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

Một yêu cầu rất quan trọng là đó PUTlà idempotent: nếu bạn yêu cầu tác dụng phụ khi cập nhật Nhóm (hoặc thay đổi kích hoạt), bạn nên sử dụng PATCH. Vì vậy, khi cập nhật kết quả, ví dụ như gửi thư, không sử dụng PUT.


3
Điều này rất nhiều thông tin cho tôi. "Mẫu này hữu ích khi" kích hoạt "của nhóm có tác dụng phụ" - Mẫu này hữu ích như thế nào, cụ thể là khi hành động có tác dụng phụ, trái ngược với các điểm cuối ban đầu của OP
Abdul

1
@Abdul, mẫu này rất hữu ích vì nhiều lý do, nhưng tác dụng phụ của wrt, nó phải rất rõ ràng với khách hàng, tác động của một hành động là gì. Khi, giả sử, một ứng dụng iOS quyết định gửi toàn bộ sổ địa chỉ dưới dạng "danh bạ" thì sẽ cực kỳ rõ ràng những tác dụng phụ nào trong việc tạo, cập nhật, xóa, v.v. của một Liên hệ có. Để tránh gửi thư hàng loạt tất cả các địa chỉ liên lạc, ví dụ.
cập bến

1
Trong RESTfull PUT cũng có thể thay đổi Danh tính thực thể - Ví dụ: ID Chính, nơi nó có thể khiến yêu cầu song song không thành công. (ví dụ: cập nhật toàn bộ thực thể cần xóa một số hàng và thêm hàng mới, từ đó tạo ra các thực thể mới) Trong đó PATCH không bao giờ có thể làm điều đó, cho phép không giới hạn số lượng yêu cầu PATCH mà không ảnh hưởng đến các "ứng dụng" khác
Piotr Kula

1
Câu trả lời rất hữu ích. Cảm ơn! Tôi cũng sẽ thêm một nhận xét, giống như trong câu trả lời của Luke, chỉ ra rằng sự khác biệt giữa PUT / PATCH không chỉ là cập nhật toàn bộ / một phần, mà còn là sự bình thường khác biệt. Đây không phải là một sai lầm, đó là một quyết định có chủ ý và tôi nghĩ rằng không có nhiều người cân nhắc điều này khi quyết định sử dụng phương thức HTTP.
Mladen B.

1
Các dịch vụ @richremer, giống như các mô hình, là trừu tượng nội bộ. Giống như việc trừu tượng hóa kém để yêu cầu mối quan hệ 1-1 giữa các mô hình REST-endpoint-và-ORM hoặc thậm chí các bảng cơ sở dữ liệu, việc trừu tượng hóa dịch vụ là không tốt. API bên ngoài sẽ phải giao tiếp với các mô hình miền. Cách bạn triển khai chúng trong nội bộ không liên quan đến API. Bạn có thể tự do chuyển từ ActivationService sang luồng kích hoạt dựa trên CQRS mà không phải thay đổi API.
cập bến

12

Tôi khuyên bạn nên sử dụng PATCH, vì 'nhóm' tài nguyên của bạn có nhiều thuộc tính nhưng trong trường hợp này, bạn chỉ cập nhật trường kích hoạt (sửa đổi một phần)

theo RFC5789 ( https://tools.ietf.org/html/rfc5789 )

Phương thức HTTP PUT hiện tại chỉ cho phép thay thế hoàn toàn một tài liệu. Đề xuất này thêm một phương thức HTTP mới, PATCH, để sửa đổi tài nguyên HTTP hiện có.

Ngoài ra, chi tiết hơn,

Sự khác biệt giữa các yêu cầu PUT và PATCH được phản ánh trong cách máy chủ xử lý thực thể kèm theo để sửa đổi tài nguyên
được xác định bởi URI yêu cầu. Trong yêu cầu PUT, thực thể kèm theo được coi là phiên bản sửa đổi của tài nguyên được lưu trữ trên
máy chủ gốc và máy khách đang yêu cầu
thay thế phiên bản đã lưu trữ . Tuy nhiên, với PATCH, thực thể kèm theo chứa một tập hợp các hướng dẫn mô tả cách một tài nguyên hiện đang cư trú trên
máy chủ gốc nên được sửa đổi để tạo ra một phiên bản mới. Phương thức PATCH ảnh hưởng đến tài nguyên được xác định bởi URI yêu cầu và
cũng có thể có tác dụng phụ đối với các tài nguyên khác; tức là tài nguyên mới
có thể được tạo ra, hoặc những cái hiện có được sửa đổi, bằng cách áp dụng
VÁCH.

PATCH không an toàn cũng không bình thường như được định nghĩa bởi [RFC2616], Mục 9.1.

Khách hàng cần chọn thời điểm sử dụng VÒI thay vì PUT. Ví
dụ: nếu kích thước tài liệu vá lớn hơn kích thước của
dữ liệu tài nguyên mới sẽ được sử dụng trong PUT, thì có thể
sử dụng PUT thay vì PATCH. Việc so sánh với POST thậm chí còn khó khăn hơn, bởi vì POST được sử dụng theo nhiều cách khác nhau và có thể
bao gồm các hoạt động giống như PUT và PATCH nếu máy chủ chọn. Nếu
hoạt động không sửa đổi tài nguyên được xác định bởi URI yêu cầu theo cách có thể dự đoán được, thì POST nên được xem xét thay vì PATCH
hoặc PUT.

Mã phản hồi cho PATCH là

Mã phản hồi 204 được sử dụng vì phản hồi không mang nội dung thư (mà phản hồi với mã 200 sẽ có). Lưu ý rằng các mã thành công khác cũng có thể được sử dụng.

đồng thời tham khảo thttp: //restcookbook.com/HTTP%20Methods/patch/

Hãy cẩn thận: Một API triển khai PATCH phải vá nguyên tử. Không được phép các tài nguyên được vá một nửa khi được GET yêu cầu.


7

Vì bạn muốn thiết kế API bằng cách sử dụng kiểu kiến ​​trúc REST, bạn cần suy nghĩ về các trường hợp sử dụng của mình để quyết định khái niệm nào đủ quan trọng để thể hiện dưới dạng tài nguyên. Nếu bạn quyết định hiển thị trạng thái của một nhóm dưới dạng tài nguyên phụ, bạn có thể cung cấp cho URI sau đây và triển khai hỗ trợ cho cả hai phương thức GET và PUT:

/groups/api/groups/{group id}/status

Nhược điểm của phương pháp này so với PATCH để sửa đổi là bạn sẽ không thể thực hiện thay đổi đối với nhiều tài sản của một nhóm về mặt nguyên tắc và giao dịch. Nếu thay đổi giao dịch là quan trọng thì hãy sử dụng VÁCH.

Nếu bạn quyết định để lộ trạng thái như một tài nguyên phụ của một nhóm thì đó phải là một liên kết trong đại diện của nhóm. Ví dụ: nếu tác nhân được nhóm 123 và chấp nhận XML, phần phản hồi có thể chứa:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

Một siêu liên kết là cần thiết để thực hiện hypermedia như là công cụ của điều kiện trạng thái ứng dụng của kiểu kiến ​​trúc REST.


0

Tôi thường thích một cái gì đó đơn giản hơn một chút, như activate/ deactivatetài nguyên phụ (được liên kết bởi một Linktiêu đề với rel=service).

POST /groups/api/v1/groups/{group id}/activate

hoặc là

POST /groups/api/v1/groups/{group id}/deactivate

Đối với người tiêu dùng, giao diện này rất đơn giản và nó tuân theo các nguyên tắc REST mà không khiến bạn sa lầy trong việc khái niệm "kích hoạt" thành các tài nguyên riêng lẻ.


0

Một lựa chọn khả thi để thực hiện hành vi đó là

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

Và rõ ràng, nếu ai đó cần hủy kích hoạt nó, PUTsẽ có Deactivatedtrạng thái trong JSON.

Trong trường hợp cần thiết phải kích hoạt / hủy kích hoạt hàng loạt, PATCHcó thể bước vào trò chơi (không phải cho nhóm chính xác, mà là cho groupstài nguyên:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

Nói chung, đây là ý tưởng như @Andrew Dobrowolski gợi ý, nhưng với những thay đổi nhỏ trong việc thực hiện chính xác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.