REST-是否将ID放在正文中?


95

假设我想为人们提供一个RESTful资源,客户端可以在其中分配ID。

一个人看起来像这样: {"id": <UUID>, "name": "Jimmy"}

现在,客户端应如何保存(或“放置”)它?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} -现在我们要重复验证这个讨厌的重复:正文中的ID是否与路径中的ID匹配?
  2. 不对称表示:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID 退货 {"id": <UUID>, "name": "Jimmy"}
  3. 正文中没有ID-仅在以下位置具有ID:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID 退货 {"name": "Jimmy"}
  4. POST由于ID是由客户端生成的,因此似乎没有什么好主意。

有哪些常见的模式和解决方法?仅在位置上的ID似乎是最教条化的正确方法,但这也使实际实现更加困难。

Answers:


62

拥有不同的读/写模型是没有错的:客户端可以编写一个资源表示形式,在服务器可以返回其中包含添加/计算元素的另一个表示形式(甚至是完全不同的表示形式-在任何规范中都没有反对者) ,唯一的要求是PUT应该创建或替换资源)。

因此,我将在(2)中采用非对称解决方案,并在编写时避免服务器端的“讨厌的重复检查”:

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

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

1
而且,如果您应用打字(静态或动态),就不会无ID地创建模型...因此,从PUT请求的URL中删除ID更容易。这不会是“烦人的”,但它是正确的。
伊万·克莱辛

1
保留附加的TO,而不id能将其与ID和实体以及附加的转换器一起包含TO,这对于程序员来说太大了。
Grigory Kislin's

如果我从BODY处获得ID,例如:PUT / person {“ id”:1,“ name”:“ Jimmy”}怎么办?那会是个坏习惯吗?
布鲁诺·桑托斯

将ID放在正文中就可以了。使用GUID作为ID而不是整数-否则,存在冒着ID重复的风险。
约恩·怀尔德(JørnWildt)

错了 看我的答案。PUT必须包含整个资源。如果要排除ID并仅更新部分记录,请使用PATCH。
CompEng88

26

如果是公共API,则在回复时应保守一些,但要接受。

我的意思是,您应该同时支持1和2。我同意3没有意义。

同时支持1和2的方法是:如果请求正文中未提供ID,则从网址中获取ID;如果请求正文中未提供ID,则请验证其是否与网址中的ID相匹配。如果两者不匹配,则返回400 Bad Request响应。

返回人员资源时,请保持保守,并且始终将id包含在json中,即使在put中它是可选的。


3
这应该是公认的解决方案。API应该始终是用户友好的。它在主体中应该是可选的。我不应该从POST收到ID,然后必须在PUT中将其设为未定义。此外,所作出的400个响应点就正确了。
迈克尔

大约有400个代码,另请参见softwareengineering.stackexchange.com/questions/329229/…讨论。总之一个400码并无不当,只是不太具体,比较422
格里戈里Kislin

8

该问题的一种解决方案涉及一些令人困惑的概念,即“超文本作为应用程序状态的引擎”或“ HATEOAS”。这意味着REST响应包含要作为超链接执行的可用资源或操作。使用此方法(这是REST原始概念的一部分),资源的唯一标识符/ ID本身就是超链接。因此,例如,您可能会遇到以下情况:

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

然后,如果要更新该资源,可以执行(伪代码):

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

这样做的一个好处是,客户端不必对服务器的用户ID的内部表示有任何了解。只要客户端有发现ID的方法,ID可能会更改,甚至URL本身也会更改。例如,在获取人员集合时,您可以返回如下响应:

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

(当然,您还可以根据应用程序的需要为每个人返回完整的人对象)。

使用这种方法,您可以在资源和位置方面更多地考虑对象,而在ID方面则更少。因此,唯一标识符的内部表示与客户端逻辑分离。这是REST背后的原始动力:通过使用HTTP的功能,创建比以前存在的RPC系统更松散耦合的客户端-服务器体系结构。有关HATEOAS的更多信息,请参阅Wikipedia文章以及此简短文章


4

在插入中,您不需要在URL中添加ID。这样,如果您在PUT中发送ID,则可以将其解释为UPDATE以更改主键。

  1. 插:

    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. 更新

    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"}
    

JSON API使用这个标准,并解决了一些问题,一个链接到新的对象返回插入或更新的对象。某些更新或插入内容可能包含一些将更改其他字段的业务逻辑

您还将看到插入和更新后可以避免获取。



3

仅供参考,这里的答案是错误的。

看到:

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

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

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

主要使用PUT API更新现有资源(如果资源不存在,则API可能会决定是否创建新资源)。如果PUT API已创建新资源,则原始服务器务必通过HTTP响应代码201(已创建)响应通知用户代理,并且如果修改了现有资源,则为200(确定)或204(无内容)应发送响应代码以指示请求已成功完成。

如果请求通过缓存,并且Request-URI标识一个或多个当前缓存的实体,则应将这些条目视为过期。此方法的响应不可缓存。

当您想要修改已经是资源集合一部分的单个资源时,请使用PUT。PUT会全部替换资源。如果请求更新了部分资源,请使用PATCH。

补丁

HTTP PATCH请求将对资源进行部分更新。如果您看到PUT请求也修改了资源实体,以便更加清楚地了解– PATCH方法是部分更新现有资源的正确选择,并且仅当要替换整个资源时才应使用PUT。

因此,您应该以这种方式使用它:

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"

RESTful的实践表明,您在/ {id}处输入的内容无关紧要-记录的内容应更新为有效负载提供的内容-但GET / {id}仍应链接到同一资源。

换句话说,PUT / 3可能会更新为有效负载id为4,但是GET / 3仍应链接到相同的有效负载(并返回ID设置为4的有效负载)。

如果您确定您的API在URI和有效载荷中需要相同的标识符,则确保它匹配是您的工作,但是如果您要排除有效载荷中应该完整存在的id,则一定要使用PATCH而不是PUT 。这是公认的答案有误的地方。PUT必须替换整个资源,而补丁可能是部分资源。


2

虽然可以针对不同的操作使用不同的表示形式,但对于PUT的一般建议是包含WHOLE有效负载。那意味着那也id应该在那里。否则,您应该使用PATCH。

话虽如此,我认为PUT应该主要用于更新,并且也id应该始终在URL中传递。结果,使用PUT更新资源标识符不是一个好主意。当idURL中的内容可能id与正文中的内容不同时,这使我们处于不希望的情况。

那么,我们如何解决这种冲突呢?我们基本上有2个选择:

  • 抛出4XX异常
  • 添加WarningX-API-Warn等)标头。

这与我回答这个问题就近了,因为该主题通常是一个见解问题。


1

使用不同的方法没有什么不好。但我认为最好的方法是2nd的解决方案。

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

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

它主要是在这种方式中使用即使是实体框架使用这种技术时,实体的DbContext添加类而无需通过在实体框架参考生成的ID所生成的ID。


1

我正在从JSON-LD /语义Web的角度看这件事,因为这是实现真正的REST一致性的好方法,正如我在这些幻灯片中概述的那样。从这个角度来看,毫无疑问,选项(1.)是可行的,因为Web资源的ID(IRI)应该始终等于可以用来查找/取消引用该资源的URL。我认为验证并不是很难实施,也不是计算密集型的。因此,我认为这不是选择(2.)的正当理由。我认为选项(3.)并不是真正的选项,因为POST(新建)与PUT(更新/替换)具有不同的语义。


0

您可能需要研究PATCH / PUT请求类型。

PATCH请求用于部分更新资源,而在PUT请求中,您必须将整个资源发送到服务器上被覆盖的位置。

就URL中的ID而言,我认为您应该始终使用它,因为识别资源是一种标准做法。甚至Stripe API都可以这样工作。

您可以使用PATCH请求以ID来更新服务器上的资源以标识该资源,但不更新实际ID。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.