REST - đưa ID vào nội dung hay không?


96

Giả sử tôi muốn có một tài nguyên RESTful cho mọi người, nơi khách hàng có thể chỉ định ID.

Một người trông như thế này: {"id": <UUID>, "name": "Jimmy"}

Bây giờ, khách hàng nên lưu (hoặc "PUT") nó như thế nào?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - bây giờ chúng tôi có sự trùng lặp khó chịu này mà chúng tôi phải xác minh mọi lúc: ID trong nội dung có khớp với ID trong đường dẫn không?
  2. Biểu diễn không đối xứng:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID trả lại {"id": <UUID>, "name": "Jimmy"}
  3. Không có ID trong nội dung - Chỉ ID ở vị trí:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID trả lại {"name": "Jimmy"}
  4. Không có loại nào POSTcó vẻ là một ý tưởng hay vì ID được tạo bởi khách hàng.

Các mẫu phổ biến và cách giải quyết nó là gì? ID chỉ ở vị trí có vẻ là cách đúng đắn nhất về mặt giáo điều, nhưng nó cũng khiến việc triển khai thực tế khó khăn hơn.

Câu trả lời:


62

Không có gì sai khi có các mô hình đọc / ghi khác nhau: máy khách có thể viết một biểu diễn tài nguyên mà sau đó máy chủ có thể trả về một biểu diễn khác với các phần tử được thêm / tính toán trong đó (hoặc thậm chí là một biểu diễn hoàn toàn khác - không có bất kỳ thông số kỹ thuật nào chống lại điều đó , yêu cầu duy nhất là PUT phải tạo hoặc thay thế tài nguyên).

Vì vậy, tôi sẽ đi cho giải pháp bất đối xứng trong (2) và tránh "kiểm tra trùng lặp khó chịu" ở phía máy chủ khi viết:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

2
Và nếu bạn áp dụng cách nhập (tĩnh hoặc động), bạn không thể dễ dàng có các mô hình không có ID ... Vì vậy, việc xóa ID khỏi URL cho các yêu cầu PUT sẽ dễ dàng hơn nhiều. Nó sẽ không phải là "nghỉ ngơi" nhưng nó sẽ chính xác.
Ivan Kleshnin

2
Giữ thêm TO mà không idcó TO cùng với id và thực thể và các bộ chuyển đổi bổ sung và chi phí quá lớn cho các lập trình viên.
Grigory Kislin

Điều gì sẽ xảy ra nếu tôi nhận được ID từ BODY, ví dụ: PUT / person {"id": 1, "name": "Jimmy"}. Đó sẽ là một thực hành xấu?
Bruno Santos

Đưa ID vào cơ thể sẽ ổn. Sử dụng GUID cho ID thay vì một số nguyên - nếu không, bạn có nguy cơ bị trùng lặp ID.
Jørn Wildt

Cái này sai. Hãy xem câu trả lời của tôi. PUT phải chứa toàn bộ tài nguyên. Sử dụng PATCH nếu bạn muốn loại trừ id và chỉ cập nhật các phần của bản ghi.
CompEng88

27

Nếu đó là một API công khai, bạn nên thận trọng khi trả lời, nhưng hãy chấp nhận một cách tự do.

Ý tôi là, bạn nên ủng hộ cả 1 và 2. Tôi đồng ý rằng 3 không có ý nghĩa.

Cách để hỗ trợ cả 1 và 2 là lấy id từ url nếu không có id nào được cung cấp trong phần thân yêu cầu và nếu nó nằm trong phần thân yêu cầu, thì hãy xác thực rằng nó khớp với id trong url. Nếu cả hai không khớp, thì trả về 400 phản hồi Yêu cầu Không hợp lệ.

Khi trả về một tài nguyên người, hãy thận trọng và luôn bao gồm id trong json, mặc dù nó là tùy chọn trong đặt.


3
Đây nên là giải pháp được chấp nhận. API phải luôn thân thiện với người dùng. Nó phải là tùy chọn trong cơ thể. Tôi không nên nhận ID từ POST và sau đó phải đặt nó là không xác định trong PUT. Ngoài ra, 400 điểm phản hồi được thực hiện là đúng.
Michael

Khoảng 400 mã xem thêm softwareengineering.stackexchange.com/questions/329229/… thảo luận. Trong ngắn hạn, mã 400 không phù hợp, chỉ ít cụ thể hơn, so với mã 422.
Grigory Kislin

8

Một giải pháp cho vấn đề này liên quan đến khái niệm hơi khó hiểu về "Hypertext As The Engine Of Application State" hoặc "HATEOAS". Điều này có nghĩa là một phản hồi REST chứa các tài nguyên có sẵn hoặc các hành động được thực hiện dưới dạng siêu liên kết. Sử dụng phương pháp này, là một phần của khái niệm ban đầu về REST, các mã định danh / ID duy nhất của tài nguyên chính là siêu liên kết. Vì vậy, ví dụ, bạn có thể có một cái gì đó như:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Sau đó, nếu bạn muốn cập nhật tài nguyên đó, bạn có thể làm (mã giả):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Một lợi thế của điều này là máy khách không phải có bất kỳ ý tưởng nào về việc trình bày nội bộ của máy chủ đối với ID người dùng. Các ID có thể thay đổi và ngay cả bản thân các URL cũng có thể thay đổi, miễn là khách hàng có cách phát hiện ra chúng. Ví dụ: khi nhận được một tập hợp nhiều người, bạn có thể trả lại phản hồi như sau:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Tất nhiên, bạn cũng có thể trả về đối tượng đầy đủ cho mỗi người, tùy thuộc vào nhu cầu của ứng dụng).

Với phương pháp này, bạn nghĩ về các đối tượng của mình nhiều hơn về tài nguyên và vị trí, và ít hơn về ID. Do đó, đại diện nội bộ của số nhận dạng duy nhất được tách ra khỏi logic khách hàng của bạn. Đây là động lực ban đầu đằng sau REST: tạo ra các kiến ​​trúc máy khách-máy chủ được kết hợp lỏng lẻo hơn các hệ thống RPC đã tồn tại trước đây, bằng cách sử dụng các tính năng của HTTP. Để biết thêm thông tin về HATEOAS, hãy xem bài viết trên Wikipedia cũng như bài viết ngắn này .


4

Trong phần chèn, bạn không cần thêm id trong URL. Bằng cách này nếu bạn gửi ID trong PUT, bạn có thể hiểu là CẬP NHẬT để thay đổi khóa chính.

  1. CHÈN:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. CẬP NHẬT

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

Các JSON API sử dụng tiêu chuẩn này và giải quyết một số vấn đề trả lại đối tượng chèn hoặc cập nhật với một liên kết đến các đối tượng mới. Một số cập nhật hoặc phụ trang có thể bao gồm một số logic nghiệp vụ sẽ thay đổi các trường bổ sung

Bạn cũng sẽ thấy rằng bạn có thể tránh được sau khi chèn và cập nhật.


3

Điều này đã được hỏi trước đây - cuộc thảo luận đáng xem:

Một phản hồi RESTful GET có nên trả về ID của tài nguyên không?

Đây là một trong những câu hỏi khiến bạn dễ bị sa lầy vào cuộc tranh luận xung quanh điều gì được và không phải là "RESTful" .

Đối với những gì nó đáng giá, tôi cố gắng suy nghĩ về các nguồn lực nhất quán và không thay đổi thiết kế của chúng giữa các phương pháp. Tuy nhiên, IMHO điều quan trọng nhất từ ​​góc độ khả năng sử dụng là bạn phải nhất quán trên toàn bộ API!


2

Mặc dù có thể có các đại diện khác nhau cho các hoạt động khác nhau, nhưng khuyến nghị chung cho PUT là chứa tải trọng TOÀN BỘ . Đó nghĩa làid cũng nên có. Nếu không, bạn nên sử dụng PATCH.

Đã nói rằng, tôi nghĩ PUT chủ yếu nên được sử dụng cho các bản cập nhật và idluôn phải được chuyển trong URL. Do đó, sử dụng PUT để cập nhật mã định danh tài nguyên là một ý tưởng tồi. Nó khiến chúng ta idrơi vào tình huống không mong muốn khi trong URL có thể khác vớiid trong nội dung.

Vì vậy, làm thế nào để chúng ta giải quyết một xung đột như vậy? Về cơ bản chúng tôi có 2 lựa chọn:

  • ném một ngoại lệ 4XX
  • thêm tiêu đề Warning( X-API-Warnv.v.).

Đó là gần như tôi có thể trả lời câu hỏi này vì chủ đề nói chung là một vấn đề quan điểm.


2

Chỉ là FYI, câu trả lời ở đây là sai.

Xem:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

ĐẶT

Sử dụng các API PUT chủ yếu để cập nhật tài nguyên hiện có (nếu tài nguyên không tồn tại, thì API có thể quyết định tạo tài nguyên mới hay không). Nếu tài nguyên mới đã được tạo bởi API PUT, máy chủ gốc PHẢI thông báo cho tác nhân người dùng thông qua phản hồi mã phản hồi HTTP 201 (Đã tạo) và nếu tài nguyên hiện có được sửa đổi, thì 200 (OK) hoặc 204 (Không có nội dung) mã phản hồi NÊN được gửi để cho biết yêu cầu đã hoàn thành thành công.

Nếu yêu cầu đi qua bộ đệm và URI yêu cầu xác định một hoặc nhiều thực thể hiện được lưu trong bộ đệm, thì những mục nhập đó NÊN được coi là cũ. Các phản hồi cho phương pháp này không thể lưu vào bộ nhớ cache.

Sử dụng PUT khi bạn muốn sửa đổi một tài nguyên đơn lẻ đã là một phần của bộ sưu tập tài nguyên. PUT thay thế toàn bộ tài nguyên. Sử dụng PATCH nếu yêu cầu cập nhật một phần của tài nguyên.

Yêu cầu HTTP PATCH là cập nhật một phần tài nguyên. Nếu bạn thấy các yêu cầu PUT cũng sửa đổi một thực thể tài nguyên để làm rõ hơn - phương pháp PATCH là lựa chọn chính xác để cập nhật một phần tài nguyên hiện có và PUT chỉ nên được sử dụng nếu bạn đang thay thế toàn bộ tài nguyên.

Vì vậy, bạn nên sử dụng nó theo cách này:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

Các phương pháp RESTful chỉ ra rằng việc bạn PUT tại / {id} là gì không quan trọng - nội dung của bản ghi phải được cập nhật thành nội dung do tải trọng cung cấp - nhưng GET / {id} vẫn phải liên kết đến cùng một tài nguyên.

Nói cách khác, PUT / 3 có thể cập nhật id tải trọng thành 4, nhưng GET / 3 vẫn phải liên kết với cùng một tải trọng (và trả về một có id được đặt thành 4).

Nếu bạn quyết định rằng API của mình yêu cầu cùng một số nhận dạng trong URI và trọng tải, thì nhiệm vụ của bạn là đảm bảo nó khớp, nhưng chắc chắn sử dụng PATCH thay vì PUT nếu bạn loại trừ toàn bộ id trong tải trọng phải có ở đó . Đây là nơi mà câu trả lời được chấp nhận đã sai. PUT phải thay thế toàn bộ tài nguyên, trong đó bản vá có thể là một phần.


1

Không có gì xấu khi sử dụng các cách tiếp cận khác nhau. nhưng tôi nghĩ rằng cách tốt nhất là giải pháp với thứ 2 .

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

nó chủ yếu được sử dụng theo cách này ngay cả khung thực thể cũng sử dụng kỹ thuật này khi thực thể được thêm vào trong dbContext lớp không có ID được tạo là ID được tạo bằng tham chiếu trong Entity Framework.


1

Tôi đang xem xét vấn đề này từ quan điểm JSON-LD / Semantic Web vì đó là một cách tốt để đạt được sự tuân thủ REST thực như tôi đã nêu trong các trang trình bày này . Nhìn từ góc độ đó, không có câu hỏi nào để chọn tùy chọn (1.) vì ID (IRI) của tài nguyên Web phải luôn bằng URL mà tôi có thể sử dụng để tra cứu / tham khảo tài nguyên. Tôi nghĩ việc xác minh không thực sự khó thực hiện và cũng không phải là quá trình tính toán phức tạp; vì vậy tôi không coi đây là lý do hợp lệ để đi với tùy chọn (2). Tôi nghĩ tùy chọn (3) không thực sự là một tùy chọn vì POST (tạo mới) có ngữ nghĩa khác với PUT (cập nhật / thay thế).


0

Bạn có thể cần xem xét các loại yêu cầu PATCH / PUT.

Các yêu cầu PATCH được sử dụng để cập nhật một phần tài nguyên trong khi trong các yêu cầu PUT, bạn phải gửi toàn bộ tài nguyên ở nơi nó được ghi đè trên máy chủ.

Liên quan đến việc có một ID trong url, tôi nghĩ bạn nên luôn có nó vì đây là một phương pháp tiêu chuẩn để xác định một tài nguyên. Ngay cả API Stripe cũng hoạt động theo cách đó.

Bạn có thể sử dụng yêu cầu PATCH để cập nhật tài nguyên trên máy chủ có ID để xác định nó nhưng không cập nhật ID thự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.