Sử dụng các phương thức PUT vs PATCH trong các tình huống thực tế của API REST


680

Trước hết, một số định nghĩa:

PUT được định nghĩa trong Mục 9.6 RFC 2616 :

Phương thức PUT yêu cầu thực thể kèm theo được lưu trữ theo URI yêu cầu được cung cấp. Nếu URI yêu cầu đề cập đến một tài nguyên đã tồn tại, thực thể kèm theo NÊN được coi là phiên bản sửa đổi của tài nguyên cư trú trên máy chủ gốc . Nếu URI yêu cầu không trỏ đến tài nguyên hiện có và URI đó có khả năng được xác định là tài nguyên mới bởi tác nhân người dùng yêu cầu, máy chủ gốc có thể tạo tài nguyên với URI đó.

PATCH được định nghĩa trong RFC 5789 :

Phương thức PATCH yêu cầu một tập hợp các thay đổi được mô tả trong thực thể yêu cầu được áp dụng cho tài nguyên được xác định bởi URI yêu cầu.

Ngoài ra, theo RFC 2616, Phần 9.1.2 PUT là Idempotent trong khi PATCH thì không.

Bây giờ chúng ta hãy xem một ví dụ thực tế. Khi tôi thực hiện POST /usersvới dữ liệu {username: 'skwee357', email: 'skwee357@domain.com'}và máy chủ có khả năng tạo tài nguyên, nó sẽ phản hồi với 201 và vị trí tài nguyên (giả sử /users/1) và bất kỳ cuộc gọi tiếp theo nào tới GET /users/1sẽ trả về {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Bây giờ hãy để chúng tôi nói tôi muốn sửa đổi email của tôi. Sửa đổi email được coi là "một tập hợp các thay đổi" và do đó tôi nên BẮT ĐẦU /users/1với " tài liệu vá ". Trong trường hợp của tôi, nó sẽ là tài liệu json : {email: 'skwee357@newdomain.com'}. Sau đó, máy chủ trả về 200 (giả sử sự cho phép là ok). Điều này đưa tôi đến câu hỏi đầu tiên:

  • VÒI là không bình thường. Nó đã nói như vậy trong RFC 2616 và RFC 5789. Tuy nhiên, nếu tôi đưa ra cùng một yêu cầu PATCH (với email mới của tôi), tôi sẽ nhận được trạng thái tài nguyên tương tự (với email của tôi được sửa đổi thành giá trị được yêu cầu). Tại sao PATCH không phải là idempotent?

PATCH là một động từ tương đối mới (RFC được giới thiệu vào tháng 3 năm 2010) và nó nhằm giải quyết vấn đề "vá" hoặc sửa đổi một tập hợp các trường. Trước khi PATCH được giới thiệu, mọi người đã sử dụng PUT để cập nhật tài nguyên. Nhưng sau khi PATCH được giới thiệu, nó khiến tôi bối rối về việc PUT được sử dụng để làm gì. Và điều này đưa tôi đến câu hỏi thứ hai (và chính):

  • Sự khác biệt thực sự giữa PUT và PATCH là gì? Tôi đã đọc ở đâu đó rằng PUT có thể được sử dụng để thay thế toàn bộ thực thể dưới tài nguyên cụ thể, do đó, người ta nên gửi thực thể đầy đủ (thay vì tập hợp các thuộc tính như với PATCH). Sử dụng thực tế thực tế cho trường hợp như vậy là gì? Khi nào bạn muốn thay thế / ghi đè một thực thể tại một URI tài nguyên cụ thể và tại sao một hoạt động như vậy không được xem là cập nhật / vá thực thể? Trường hợp sử dụng thực tế duy nhất tôi thấy cho PUT là phát hành PUT trên bộ sưu tập, tức là /usersđể thay thế toàn bộ bộ sưu tập. Phát hành PUT trên một thực thể cụ thể không có ý nghĩa gì sau khi PATCH được giới thiệu. Tôi có lầm không?

1
a) đó là RFC 2616, không phải 2612. b) RFC 2616 đã bị lỗi thời, thông số hiện tại của PUT là ở greenbytes.de/tech/webdav/rfc7231.html#PUT , c) Tôi không nhận được câu hỏi của bạn; Không phải là khá rõ ràng rằng PUT có thể được sử dụng để thay thế bất kỳ tài nguyên nào, không chỉ là một bộ sưu tập, d) trước khi giới thiệu PATCH, mọi người thường sử dụng POST, e) cuối cùng, vâng, một yêu cầu PATCH cụ thể (tùy thuộc vào định dạng bản vá) có thể bình thường; chỉ là nó không nói chung.
Julian Reschke

nếu nó giúp tôi đã viết một bài viết về PATCH vs PUT eq8.eu/bloss/36-patch-vs-put-and-the-patch-json-syntax-war
tương

5
Đơn giản: POST tạo một mục trong bộ sưu tập. PUT thay thế một mục. PATCH sửa đổi một mục. Khi POST, URL cho mục mới được tính toán và trả về trong phản hồi, trong khi PUT và PATCH yêu cầu URL trong yêu cầu. Đúng?
Tom Russell

Bài này có thể có ích: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
Micha

Câu trả lời:


941

LƯU Ý : Khi tôi lần đầu tiên dành thời gian đọc về REST, idempotence là một khái niệm khó hiểu để cố gắng hiểu đúng. Tôi vẫn không hiểu nó hoàn toàn đúng trong câu trả lời ban đầu của mình, vì những bình luận thêm (và câu trả lời của Jason Hoetger ) đã được hiển thị. Trong một thời gian, tôi đã chống lại việc cập nhật câu trả lời này một cách rộng rãi, để tránh đạo văn Jason một cách hiệu quả, nhưng tôi đang chỉnh sửa nó ngay bây giờ bởi vì, tôi đã được yêu cầu (trong các bình luận).

Sau khi đọc câu trả lời của tôi, tôi đề nghị bạn cũng đọc câu trả lời tuyệt vời của Jason Hoetger cho câu hỏi này và tôi sẽ cố gắng làm cho câu trả lời của mình tốt hơn mà không cần đánh cắp từ Jason.

Tại sao PUT idempotent?

Như bạn đã lưu ý trong trích dẫn RFC 2616 của bạn, PUT được coi là tạm thời. Khi bạn PUT một tài nguyên, hai giả định này đang diễn ra:

  1. Bạn đang đề cập đến một thực thể, không phải là một bộ sưu tập.

  2. Thực thể bạn đang cung cấp đã hoàn tất ( toàn bộ thực thể).

Hãy xem xét một trong những ví dụ của bạn.

{ "username": "skwee357", "email": "skwee357@domain.com" }

Nếu bạn POST tài liệu này /users, như bạn đề xuất, thì bạn có thể lấy lại một thực thể như

## /users/1

{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}

Nếu bạn muốn sửa đổi thực thể này sau, bạn chọn giữa PUT và PATCH. Một PUT có thể trông như thế này:

PUT /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // new email address
}

Bạn có thể thực hiện tương tự bằng cách sử dụng VÒI. Điều đó có thể trông như thế này:

PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

Bạn sẽ nhận thấy một sự khác biệt ngay lập tức giữa hai. PUT bao gồm tất cả các tham số trên người dùng này, nhưng PATCH chỉ bao gồm thông số đã được sửa đổi ( email).

Khi sử dụng PUT, giả định rằng bạn đang gửi thực thể hoàn chỉnh và thực thể hoàn chỉnh đó thay thế bất kỳ thực thể hiện có nào tại URI đó. Trong ví dụ trên, PUT và PATCH hoàn thành cùng một mục tiêu: cả hai đều thay đổi địa chỉ email của người dùng này. Nhưng PUT xử lý nó bằng cách thay thế toàn bộ thực thể, trong khi PATCH chỉ cập nhật các trường được cung cấp, để lại các trường khác.

Vì các yêu cầu PUT bao gồm toàn bộ thực thể, nếu bạn phát hành cùng một yêu cầu, nó sẽ luôn có cùng kết quả (dữ liệu bạn đã gửi bây giờ là toàn bộ dữ liệu của thực thể). Do đó PUT là idempotent.

Sử dụng PUT sai

Điều gì xảy ra nếu bạn sử dụng dữ liệu PATCH ở trên trong yêu cầu PUT?

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PUT /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(Tôi giả sử cho mục đích của câu hỏi này rằng máy chủ không có bất kỳ trường bắt buộc cụ thể nào và sẽ cho phép điều này xảy ra ... đó có thể không phải là trường hợp thực tế.)

Vì chúng tôi đã sử dụng PUT, nhưng chỉ được cung cấp email, nên bây giờ đó là điều duy nhất trong thực thể này. Điều này đã dẫn đến mất dữ liệu.

Ví dụ này ở đây cho mục đích minh họa - không bao giờ thực sự làm điều này. Yêu cầu PUT này là không có kỹ thuật, nhưng điều đó không có nghĩa là nó không phải là một ý tưởng tồi tệ, tồi tệ.

Làm thế nào để có thể bình tĩnh?

Trong ví dụ trên, PATCH idempotent. Bạn đã thực hiện một thay đổi, nhưng nếu bạn thực hiện cùng một thay đổi lặp đi lặp lại, nó sẽ luôn trả lại cùng một kết quả: bạn đã thay đổi địa chỉ email thành giá trị mới.

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // email address was changed
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // nothing changed since last GET
}

Ví dụ ban đầu của tôi, cố định cho chính xác

Ban đầu tôi có những ví dụ mà tôi nghĩ là thể hiện sự không bình thường, nhưng chúng sai lệch / không chính xác. Tôi sẽ giữ các ví dụ, nhưng sử dụng chúng để minh họa một điều khác: đó là nhiều tài liệu PATCH chống lại cùng một thực thể, sửa đổi các thuộc tính khác nhau, không làm cho các biểu tượng không phải là idempotent.

Hãy nói rằng tại một thời điểm trước đây, một người dùng đã được thêm vào. Đây là trạng thái mà bạn đang bắt đầu.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Sau một PATCH, bạn có một thực thể được sửa đổi:

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Nếu sau đó bạn liên tục áp dụng BỆNH NHÂN của mình, bạn sẽ tiếp tục nhận được kết quả tương tự: email đã được thay đổi thành giá trị mới. A đi vào, A đi ra, do đó đây là idempotent.

Một giờ sau, sau khi bạn đi pha cà phê và nghỉ ngơi, một người khác đi cùng với BỆNH NHÂN của chính họ. Có vẻ như Bưu điện đã và đang thực hiện một số thay đổi.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Vì cái này từ bưu điện không liên quan đến email, chỉ có mã zip, nếu nó được áp dụng nhiều lần, nó cũng sẽ nhận được kết quả tương tự: mã zip được đặt thành giá trị mới. A đi vào, A đi ra, do đó đây cũng là idempotent.

Ngày hôm sau, bạn quyết định gửi lại BỆNH NHÂN của mình.

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Bản vá của bạn có tác dụng tương tự như ngày hôm qua: nó đặt địa chỉ email. A đi vào, A đi ra, do đó đây là bình thường.

Những gì tôi đã sai trong câu trả lời ban đầu của tôi

Tôi muốn rút ra một sự khác biệt quan trọng (điều mà tôi đã sai trong câu trả lời ban đầu của mình). Nhiều máy chủ sẽ đáp ứng các yêu cầu REST của bạn bằng cách gửi lại trạng thái thực thể mới, với các sửa đổi của bạn (nếu có). Vì vậy, khi bạn nhận được phản hồi này , nó khác với phản hồi bạn nhận được ngày hôm qua , bởi vì mã zip không phải là mã bạn nhận được lần trước. Tuy nhiên, yêu cầu của bạn không liên quan đến mã zip, chỉ với email. Vì vậy, tài liệu PATCH của bạn vẫn còn trống - email bạn đã gửi trong PATCH hiện là địa chỉ email trên thực thể.

Vậy khi nào thì PATCH không bình thường thì sao?

Để điều trị đầy đủ cho câu hỏi này, một lần nữa tôi giới thiệu bạn với câu trả lời của Jason Hoetger . Tôi sẽ để nó ở đó, bởi vì tôi thực sự không nghĩ rằng tôi có thể trả lời phần này tốt hơn những gì anh ta đã có.


2
Câu này không hoàn toàn chính xác: "Nhưng nó là bình thường: bất cứ khi nào A đi vào, B luôn luôn xuất hiện". Ví dụ: nếu bạn GET /users/1trước khi Bưu điện cập nhật mã zip và sau đó lại đưa ra GET /users/1yêu cầu tương tự sau khi cập nhật của Bưu điện, bạn sẽ nhận được hai phản hồi khác nhau (mã zip khác nhau). Cùng một "A" (yêu cầu NHẬN) đang diễn ra, nhưng bạn đang nhận được kết quả khác nhau. Tuy nhiên, GET vẫn là idempotent.
Jason Hoetger

@JasonHoetger GET an toàn (được cho là không gây ra thay đổi), nhưng không phải lúc nào cũng bình thường. Có một sự khác biệt. Xem RFC 2616 giây. 9.1 .
Dan Lowe

1
@DanLowe: NHẬN chắc chắn nhất được đảm bảo là idempotent. Nó nói chính xác rằng trong Phần 9.1.2 của RFC 2616 và trong thông số kỹ thuật được cập nhật, RFC 7231, phần 4.2.2 , rằng "Trong số các phương thức yêu cầu được xác định bởi thông số kỹ thuật này, PUT, DELETE và các phương thức yêu cầu an toàn là tạm thời." Idempotence chỉ không có nghĩa là "bạn nhận được cùng một phản hồi mỗi khi bạn thực hiện cùng một yêu cầu". 7231 4.2.2 tiếp tục nói: "Lặp lại yêu cầu sẽ có tác dụng như mong muốn, ngay cả khi yêu cầu ban đầu thành công, mặc dù phản hồi có thể khác. "
Jason Hoetger

1
@JasonHoetger Tôi sẽ thừa nhận điều đó, nhưng tôi không thấy nó phải làm gì với câu trả lời này, nó đã thảo luận về PUT và PATCH và thậm chí không bao giờ đề cập đến NHẬN ...
Dan Lowe

1
À, nhận xét từ @JasonHoetger đã xóa nó đi: chỉ các trạng thái kết quả, chứ không phải là phản hồi, của nhiều yêu cầu phương thức idempotent cần phải giống hệt nhau.
Tom Russell

328

Mặc dù câu trả lời xuất sắc của Dan Lowe đã trả lời rất kỹ lưỡng câu hỏi của OP về sự khác biệt giữa PUT và PATCH, nhưng câu trả lời của nó cho câu hỏi tại sao PATCH không phải là không chính xác.

Để chỉ ra lý do tại sao PATCH không phải là idempotent, nó giúp bắt đầu với định nghĩa về idempotence (từ Wikipedia ):

Thuật ngữ idempotent được sử dụng toàn diện hơn để mô tả một hoạt động sẽ tạo ra kết quả tương tự nếu được thực hiện một lần hoặc nhiều lần [...] Hàm idempotent là một hàm có thuộc tính f (f (x)) = f (x) cho bất kỳ giá trị x.

Trong ngôn ngữ dễ tiếp cận hơn, một PATCH không cần thiết có thể được định nghĩa là: Sau khi tạo một tài nguyên với một tài liệu vá, tất cả các lệnh gọi PATCH tiếp theo đến cùng một tài nguyên với cùng một tài liệu vá sẽ không thay đổi tài nguyên.

Ngược lại, một hoạt động không phải là idempotent là một hoạt động trong đó f (f (x))! cùng một tài liệu vá làm thay đổi tài nguyên.

Để minh họa một PATCH không bình thường, giả sử có tài nguyên / người dùng và giả sử rằng cuộc gọi GET /userstrả về một danh sách người dùng, hiện tại:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

Thay vì PATCHing / users / {id}, như trong ví dụ của OP, giả sử máy chủ cho phép PATCHing / người dùng. Hãy đưa ra yêu cầu này.

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

Tài liệu vá của chúng tôi hướng dẫn máy chủ thêm người dùng mới được gọi newuservào danh sách người dùng. Sau khi gọi đây là lần đầu tiên, GET /userssẽ trở lại:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

Bây giờ, nếu chúng tôi đưa ra yêu cầu chính xác giống như trên, điều gì xảy ra? (Vì lợi ích của ví dụ này, hãy giả sử rằng tài nguyên / users cho phép trùng lặp tên người dùng.) "Op" là "add", do đó, một người dùng mới được thêm vào danh sách và GET /userstrả về sau :

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

Các / tài nguyên người dùng đã thay đổi một lần nữa , mặc dù chúng ta đã ban hành các chính xác cùng PATCH chống lại chính xác cùng một thiết bị đầu cuối. Nếu PATCH của chúng tôi là f (x), f (f (x)) không giống với f (x), và do đó, PATCH cụ thể này không phải là idempotent .

Mặc dù PATCH không được đảm bảo là idempotent, nhưng không có gì trong đặc tả của PATCH để ngăn bạn thực hiện tất cả các hoạt động của PATCH trên idempotent máy chủ cụ thể của bạn. RFC 5789 thậm chí còn dự đoán những lợi thế từ các yêu cầu PATCH không cần thiết:

Một yêu cầu PATCH có thể được ban hành theo cách không cần thiết, điều này cũng giúp ngăn ngừa các kết quả xấu từ sự va chạm giữa hai yêu cầu PATCH trên cùng một tài nguyên trong một khung thời gian tương tự.

Trong ví dụ của Dan, trên thực tế, hoạt động PATCH của anh ta là bình thường. Trong ví dụ đó, thực thể / users / 1 đã thay đổi giữa các yêu cầu PATCH của chúng tôi, nhưng không phải vì các yêu cầu PATCH của chúng tôi; thực ra đó là tài liệu vá khác nhau của Bưu điện khiến mã zip thay đổi. Khác biệt của Bưu điện là một hoạt động khác nhau; nếu PATCH của chúng tôi là f (x), thì PATCH của Bưu điện là g (x). Idempotence nói rằng f(f(f(x))) = f(x), nhưng không bảo đảm về f(g(f(x))).


11
Giả sử rằng máy chủ cũng cho phép phát hành PUT tại /users, điều này cũng sẽ khiến PUT trở nên không bình thường. Tất cả điều này dẫn đến là cách máy chủ được thiết kế để xử lý các yêu cầu.
Uzair Sajid

13
Vì vậy, chúng tôi có thể xây dựng một API chỉ với các hoạt động PATCH. Sau đó, điều gì trở thành nguyên tắc REST của việc sử dụng http VERBS để thực hiện các hành động CRUD trên Tài nguyên? Không phải chúng ta đang phản ứng thái quá với các quý ông biên giới ở đây sao?
bohr

6
Nếu PUT được triển khai trên một bộ sưu tập (ví dụ /users), mọi yêu cầu PUT sẽ thay thế nội dung của bộ sưu tập đó. Vì vậy, một PUT /userssẽ mong đợi một bộ sưu tập người dùng và xóa tất cả những người khác. Điều này là bình thường. Không có khả năng bạn làm điều đó trên điểm cuối / người dùng. Nhưng một cái gì đó giống như /users/1/emailscó thể là một bộ sưu tập và nó có thể hoàn toàn hợp lệ để cho phép thay thế toàn bộ bộ sưu tập bằng một bộ sưu tập mới.
Vectorjohn

5
Mặc dù câu trả lời này cung cấp một ví dụ tuyệt vời về sự bình tĩnh, tôi tin rằng điều này có thể làm vẩn đục nước trong các kịch bản REST điển hình. Trong trường hợp này, bạn có một yêu cầu PATCH với một ophành động bổ sung đang kích hoạt logic phía máy chủ cụ thể. Điều này sẽ yêu cầu máy chủ và máy khách nhận thức được các giá trị cụ thể để truyền cho optrường để kích hoạt quy trình công việc phía máy chủ. Trong các kịch bản REST đơn giản hơn, loại opchức năng này là thực tiễn xấu và có thể sẽ được xử lý trực tiếp thông qua các động từ HTTP.
ivandov

7
Tôi sẽ không bao giờ xem xét việc phát hành một BCHNG, chỉ BÀI ĐĂNG và XÓA, đối với một bộ sưu tập. Điều này thực sự bao giờ được thực hiện? Do đó, PATCH có thể được coi là idempotent cho tất cả các mục đích thực tế?
Tom Russell

72

Tôi đã tò mò về điều này là tốt và tìm thấy một vài bài viết thú vị. Tôi có thể không trả lời câu hỏi của bạn đến mức đầy đủ, nhưng điều này ít nhất cung cấp thêm một số thông tin.

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFC chỉ định rằng PUT phải lấy một đại diện tài nguyên hoàn toàn mới làm thực thể yêu cầu. Điều này có nghĩa là nếu ví dụ chỉ cung cấp một số thuộc tính nhất định, thì những thuộc tính đó sẽ bị xóa (nghĩa là được đặt thành null).

Cho rằng, sau đó một PUT sẽ gửi toàn bộ đối tượng. Ví dụ,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}

Điều này sẽ cập nhật email một cách hiệu quả. Lý do PUT có thể không quá hiệu quả là vì bạn chỉ thực sự sửa đổi một trường và bao gồm tên người dùng là vô dụng. Ví dụ tiếp theo cho thấy sự khác biệt.

/users/1
PUT {id: 1, email: 'newemail@domain.com'}

Bây giờ, nếu PUT được thiết kế theo thông số kỹ thuật, thì PUT sẽ đặt tên người dùng thành null và bạn sẽ lấy lại được thông tin sau.

{id: 1, username: null, email: 'newemail@domain.com'}

Khi bạn sử dụng một PATCH, bạn chỉ cập nhật trường bạn chỉ định và để phần còn lại một mình như trong ví dụ của bạn.

Cách thực hiện sau đây trên PATCH có một chút khác biệt so với tôi chưa từng thấy trước đây.

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

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à, các tài nguyên mới có thể được tạo ra, hoặc các tài nguyên hiện có được sửa đổi, bằng cách áp dụng BCHNG.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

Bạn ít nhiều coi việc xử lý BCHNG như một cách để cập nhật một lĩnh vực. Vì vậy, thay vì gửi qua đối tượng một phần, bạn đang gửi qua hoạt động. tức là thay thế email bằng giá trị.

Bài viết kết thúc với điều này.

Điều đáng nói là PATCH không thực sự được thiết kế cho các API REST thực sự, vì luận văn của Fielding không định nghĩa bất kỳ cách nào để sửa đổi một phần tài nguyên. Tuy nhiên, chính Roy Fielding đã nói rằng PATCH là thứ gì đó [ông] đã tạo cho đề xuất HTTP / 1.1 ban đầu vì PUT một phần không bao giờ là RESTful. Chắc chắn rằng bạn không chuyển một đại diện hoàn chỉnh, nhưng REST không yêu cầu các đại diện phải hoàn thành.

Bây giờ, tôi không biết nếu tôi đặc biệt đồng ý với bài báo như nhiều nhà bình luận chỉ ra. Gửi qua một đại diện một phần có thể dễ dàng là một mô tả về các thay đổi.

Đối với tôi, tôi đang sử dụng hỗn hợp. Đối với hầu hết các phần, tôi sẽ coi PUT là một VÒI vì sự khác biệt thực sự duy nhất tôi nhận thấy cho đến nay là PUT "nên" đặt các giá trị bị thiếu thành null. Nó có thể không phải là cách 'chính xác nhất' để làm điều đó, nhưng chúc may mắn hoàn hảo.


7
Có thể đáng để thêm: trong bài viết của William Durand (và rfc 6902) có những ví dụ trong đó "op" là "add". Điều này rõ ràng là không bình thường.
Julian Brodwall 3/03/2016

2
Hoặc bạn có thể làm cho dễ dàng hơn và sử dụng RFC 7394 Merge Patch thay thế và tránh xây dựng bản vá JSON.
Piotr Kula

đối với các bảng nosql, sự khác biệt giữa patch và put rất quan trọng, bởi vì nosql không có cột
stackdave

18

Sự khác biệt giữa PUT và PATCH là:

  1. PUT được yêu cầu là idempotent. Để đạt được điều đó, bạn phải đặt toàn bộ tài nguyên trong phần yêu cầu.
  2. PATCH có thể không bình thường. Điều này ngụ ý nó cũng có thể là idempotent trong một số trường hợp, chẳng hạn như các trường hợp bạn mô tả.

PATCH yêu cầu một số "ngôn ngữ vá" để báo cho máy chủ biết cách sửa đổi tài nguyên. Người gọi và máy chủ cần xác định một số "thao tác" như "thêm", "thay thế", "xóa". Ví dụ:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "abc@myemail.com"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "abc@myemail.com",
  "state": "NY",
  "address": "123 main street",
}

Thay vì sử dụng các trường "hoạt động" rõ ràng, ngôn ngữ vá có thể làm cho nó ẩn bằng cách xác định các quy ước như:

trong cơ thể yêu cầu PATCH:

  1. Sự tồn tại của một trường có nghĩa là "thay thế" hoặc "thêm" trường đó.
  2. Nếu giá trị của một trường là null, có nghĩa là xóa trường đó.

Với quy ước trên, PATCH trong ví dụ có thể có dạng sau:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "abc@myemail.com",
  "zip":
}

Mà trông súc tích và thân thiện hơn. Nhưng người dùng cần phải nhận thức được các quy ước cơ bản.

Với các hoạt động tôi đã đề cập ở trên, PATCH vẫn còn bình thường. Nhưng nếu bạn xác định các hoạt động như: "gia tăng" hoặc "nối thêm", bạn có thể dễ dàng thấy nó sẽ không còn tồn tại nữa.


7

TLDR - Phiên bản chết lặng

PUT => Đặt tất cả các thuộc tính mới cho tài nguyên hiện có.

PATCH => Cập nhật một phần tài nguyên hiện có (không phải tất cả các thuộc tính bắt buộc).


3

Hãy để tôi trích dẫn và nhận xét kỹ hơn về RFC 7231 phần 4.2.2 , đã được trích dẫn trong các bình luận trước đó:

Một phương thức yêu cầu được coi là "idempotent" nếu hiệu ứng dự định trên máy chủ của nhiều yêu cầu giống hệt với phương thức đó giống như hiệu ứng cho một yêu cầu như vậy. Trong số các phương thức yêu cầu được xác định bởi thông số kỹ thuật này, PUT, DELETE và các phương thức yêu cầu an toàn là tạm thời.

(...)

Các phương thức tạm thời được phân biệt vì yêu cầu có thể được lặp lại tự động nếu xảy ra lỗi giao tiếp trước khi máy khách có thể đọc phản hồi của máy chủ. Ví dụ: nếu khách hàng gửi yêu cầu PUT và kết nối cơ bản bị đóng trước khi nhận được bất kỳ phản hồi nào, thì khách hàng có thể thiết lập kết nối mới và thử lại yêu cầu tạm thời. Nó biết rằng việc lặp lại yêu cầu sẽ có tác dụng như mong muốn, ngay cả khi yêu cầu ban đầu thành công, mặc dù phản hồi có thể khác nhau.

Vậy, cái gì sẽ là "giống nhau" sau một yêu cầu lặp đi lặp lại của một phương thức idempotent? Không phải trạng thái máy chủ, cũng không phải phản hồi của máy chủ, mà là hiệu ứng mong muốn . Cụ thể, phương thức này phải là idempotent "theo quan điểm của khách hàng". Bây giờ, tôi nghĩ rằng quan điểm này cho thấy ví dụ cuối cùng trong câu trả lời của Dan Lowe , mà tôi không muốn đạo văn ở đây, thực sự cho thấy rằng một yêu cầu PATCH có thể không bình thường (theo cách tự nhiên hơn so với ví dụ trong Câu trả lời của Jason Hoetger ).

Thật vậy, hãy làm cho ví dụ chính xác hơn một chút bằng cách đưa ra ý định rõ ràng có thể cho khách hàng đầu tiên. Giả sử khách hàng này đi qua danh sách người dùng với dự án để kiểm tra email mã zip của họ . Anh ta bắt đầu với người dùng 1, thông báo rằng zip là đúng nhưng email sai. Anh ta quyết định sửa lỗi này với một yêu cầu PATCH, hoàn toàn hợp pháp và chỉ gửi

PATCH /users/1
{"email": "skwee357@newdomain.com"}

vì đây là sự điều chỉnh duy nhất Bây giờ, yêu cầu không thành công do một số sự cố mạng và được gửi lại tự động một vài giờ sau đó. Trong khi đó, một khách hàng khác (đã sửa đổi) mã zip của người dùng 1. Sau đó, gửi cùng một yêu cầu PATCH lần thứ hai không đạt được hiệu quả như mong muốn của khách hàng, vì chúng tôi kết thúc với một zip không chính xác. Do đó phương pháp này không phải là idempotent theo nghĩa của RFC.

Nếu thay vào đó, khách hàng sử dụng yêu cầu PUT để sửa email, gửi đến máy chủ tất cả các thuộc tính của người dùng 1 cùng với email, hiệu quả dự định của anh ta sẽ đạt được ngay cả khi yêu cầu phải được gửi lại sau đó và người dùng 1 đã được sửa đổi trong khi đó --- vì yêu cầu PUT thứ hai sẽ ghi đè tất cả các thay đổi kể từ yêu cầu đầu tiên.


2

Theo ý kiến ​​khiêm tốn của tôi, idempotence có nghĩa là:

  • ĐẶT:

Tôi gửi một định nghĩa tài nguyên cạnh tranh, vì vậy - trạng thái tài nguyên kết quả chính xác như được định nghĩa bởi các tham số PUT. Mỗi lần tôi cập nhật tài nguyên với cùng thông số PUT - trạng thái kết quả hoàn toàn giống nhau.

  • VÁ:

Tôi chỉ gửi một phần định nghĩa tài nguyên, vì vậy có thể xảy ra người dùng khác đang cập nhật các tham số KHÁC của tài nguyên này trong một thời gian. Do đó - các bản vá liên tiếp có cùng tham số và giá trị của chúng có thể dẫn đến trạng thái tài nguyên khác nhau. Ví dụ:

Giả sử một đối tượng được xác định như sau:

CAR: - màu: đen, - loại: sedan, - ghế: 5

Tôi vá nó bằng:

{màu đỏ'}

Đối tượng kết quả là:

CAR: - màu: đỏ, - loại: sedan, - ghế: 5

Sau đó, một số người dùng khác vá chiếc xe này bằng:

{loại: 'hatchback'}

vì vậy, đối tượng kết quả là:

XE: - màu: đỏ, - loại: hatchback, - ghế: 5

Bây giờ, nếu tôi vá đối tượng này một lần nữa với:

{màu đỏ'}

đối tượng kết quả là:

XE: - màu: đỏ, - loại: hatchback, - ghế: 5

Điều gì khác biệt với những gì tôi đã có trước đây!

Đây là lý do tại sao PATCH không phải là idempotent trong khi PUT là idempotent.


1

Để kết thúc cuộc thảo luận về tính không ổn định, tôi cần lưu ý rằng người ta có thể định nghĩa tính không ổn định trong bối cảnh REST theo hai cách. Trước tiên chúng ta hãy chính thức hóa một vài điều:

Một tài nguyên là một hàm với codomain của nó là lớp các chuỗi. Nói cách khác, tài nguyên là một tập hợp con String × Any, trong đó tất cả các khóa là duy nhất. Chúng ta hãy gọi lớp tài nguyên Res.

Một hoạt động REST trên tài nguyên, là một chức năng f(x: Res, y: Res): Res. Hai ví dụ về hoạt động REST là:

  • PUT(x: Res, y: Res): Res = x
  • PATCH(x: Res, y: Res): Res, mà hoạt động như thế nào PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Định nghĩa này được thiết kế đặc biệt để tranh luận PUTPOST, và ví dụ, không có ý nghĩa gì nhiều GETPOST, vì nó không quan tâm đến sự kiên trì).

Bây giờ, bằng cách sửa chữa x: Res(nói một cách không chính thức, sử dụng currying) PUT(x: Res)PATCH(x: Res)là các hàm đơn biến của loại Res → Res.

  1. Một hàm g: Res → Resđược gọi là idempotent toàn cầu , khi g ○ g == g, tức là cho bất kỳ y: Res, g(g(y)) = g(y).

  2. Hãy để x: Resmột tài nguyên, và k = x.keys. Một hàm g = f(x)được gọi là idempotent trái , khi cho mỗi y: Res, chúng ta có g(g(y))|ₖ == g(y)|ₖ. Về cơ bản, điều đó có nghĩa là kết quả sẽ giống nhau, nếu chúng ta nhìn vào các phím được áp dụng.

Vì vậy, PATCH(x)không phải là idempotent toàn cầu, mà là idempotent. Và tính không ổn định là vấn đề quan trọng ở đây: nếu chúng tôi vá một vài khóa của tài nguyên, chúng tôi muốn các khóa đó giống nhau nếu chúng tôi vá lại và chúng tôi không quan tâm đến phần còn lại của tài nguyên.

Và khi RFC đang nói về việc PATCH không bình thường, thì đó là nói về sự bình đẳng toàn cầu. Chà, thật tốt khi nó không phải là bình thường trên toàn cầu, nếu không nó sẽ là một hoạt động bị hỏng.


Bây giờ, câu trả lời của Jason Hoetger đang cố gắng chứng minh rằng PATCH thậm chí không còn bình tĩnh, nhưng nó phá vỡ quá nhiều thứ để làm như vậy:

  • Trước hết, PATCH được sử dụng trên một tập hợp, mặc dù PATCH được xác định để hoạt động trên các đối tượng bản đồ / từ điển / khóa-giá trị.
  • Nếu ai đó thực sự muốn áp dụng PATCH cho các bộ, thì có một bản dịch tự nhiên nên được sử dụng : t: Set<T> → Map<T, Boolean>, được định nghĩa với x in A iff t(A)(x) == True. Sử dụng định nghĩa này, vá lỗi là trái idempotent.
  • Trong ví dụ này, bản dịch này không được sử dụng, thay vào đó, PATCH hoạt động như một POST. Trước hết, tại sao một ID được tạo cho đối tượng? Và khi nào nó được tạo ra? Nếu đối tượng trước tiên được so sánh với các thành phần của tập hợp và nếu không tìm thấy đối tượng phù hợp, thì ID sẽ được tạo, sau đó chương trình sẽ hoạt động khác đi ( {id: 1, email: "me@site.com"}phải khớp với {email: "me@site.com"}, nếu không chương trình luôn bị hỏng và không thể có thể BẮT vá). Nếu ID được tạo trước khi kiểm tra đối với tập hợp, một lần nữa chương trình bị hỏng.

Người ta có thể làm cho các ví dụ về PUT là không bình thường với việc phá vỡ một nửa những thứ bị phá vỡ trong ví dụ này:

  • Một ví dụ với các tính năng bổ sung được tạo sẽ là phiên bản. Người ta có thể ghi lại số lượng thay đổi trên một đối tượng. Trong trường hợp này, PUT không phải là idempotent: PUT /user/12 {email: "me@site.com"}kết quả trong {email: "...", version: 1}lần đầu tiên và {email: "...", version: 2}lần thứ hai.
  • Lộn xộn với các ID, người ta có thể tạo một ID mới mỗi khi đối tượng được cập nhật, dẫn đến PUT không bình thường.

Tất cả các ví dụ trên là những ví dụ tự nhiên mà người ta có thể gặp phải.


Quan điểm cuối cùng của tôi là, PATCH không nên là idempotent toàn cầu , nếu không sẽ không mang lại cho bạn hiệu quả mong muốn. Bạn muốn thay đổi địa chỉ email của người dùng mà không cần chạm vào phần còn lại của thông tin và bạn không muốn ghi đè lên các thay đổi của một bên khác truy cập vào cùng một tài nguyên.


-1

Một thông tin bổ sung tôi chỉ cần thêm một là một yêu cầu PATCH sử dụng ít băng thông hơn so với yêu cầu PUT do chỉ một phần dữ liệu được gửi chứ không phải toàn bộ thực thể. Vì vậy, chỉ cần sử dụng một yêu cầu PATCH để cập nhật các bản ghi cụ thể như (1-3 bản ghi) trong khi yêu cầu PUT để cập nhật một lượng dữ liệu lớn hơn. Đó là nó, đừng suy nghĩ quá nhiều hoặc lo lắng về nó quá nhiều.

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.