RESTful API。我应该返回创建/更新的对象吗?


36

我正在使用WebApi设计RESTful Web服务,并且想知道在更新/创建对象时返回哪些HTTP响应和响应主体。

例如,我可以使用POST方法将一些JSON发送到Web服务,然后创建一个对象。然后,将HTTP状态设置为“创建”(201)或“确定”(200),并简单地返回诸如“添加了新员工”之类的消息,或者返回最初发送的对象,是最佳实践吗?

PUT方法也是如此。哪种HTTP状态最适合使用,我是否需要返回创建的对象或仅返回一条消息?考虑到用户知道他们无论如何都试图创建/更新对象的事实。

有什么想法吗?

例:

添加新员工:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

更新现有员工:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

回应:

创建/更新对象的响应

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

仅显示一条消息:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

仅带有状态码的响应:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT

2
这个问题很好,但是在这个网站上使用术语“最佳实践”是一种禁忌meta.programmers.stackexchange.com/questions/2442/…您可能只想重新表达这个问题。meta.programmers.stackexchange.com/questions/6967/…–
史努比

3
作为后续措施,在请求中添加标记是个好主意,以便(例如)移动应用程序在使用WiFi时可以返回整个对象,但在使用蜂窝数据时只能返回ID?是否应该使用标头来避免污染JSON?
安德鲁说莫妮卡(Monica)恢复

@AndrewPiliser一个有趣的想法,尽管我个人认为最好选择一种方法并坚持下去。然后,随着应用程序的开发或变得越来越流行,对其进行优化
iswinky

@AndrewPiliser您的想法与UPDATE/INSERT ... RETURNINGSQL 的Postgresql 变体非常相似。这非常方便,特别是因为它可以使新数据的提交和对更新版本的请求保持原子性。
beldaz

Answers:


31

与大多数情况一样,这取决于。您需要权衡的是易用性与网络规模。对客户来说,查看所创建的资源可能非常有帮助。它可能包含服务器填充的字段,例如上次创建时间。由于您似乎包括id而不是使用hateoas,因此客户端可能希望查看他们刚刚使用的资源的ID POST

如果您不包括创建的资源,请不要创建任意消息。2xx和位置字段足以使客户确信自己的请求已得到正确处理。


+1也可以通过允许客户端用特定ID填充服务器提供的URL模板来实现不让客户端编写uri的仇恨目标。是的,客户可以“组成”,但只能以“填补空白”的方式。尽管它不是纯粹的HATEOAS,但它可以达到目标,并使处理“大量” uri的对象的带宽敏感度降低一些,更不用说将这些对象放入(较大的)列表中了。
Marjan Venema

3
+1关于“请不要创建任意消息”的建议
HairOfTheDog

“没有任意消息”是否集中在字符串消息或不是创建资源的任何返回值上?我注重的情况下,我们返回创建资源的ID(而不是资源本身),并想知道这种结合使用。
Flater

12

就我个人而言,我总是只回来200 OK

引用你的问题

考虑到用户知道他们无论如何都试图创建/更新对象的事实。

为什么要增加额外的带宽(可能需要付费)以告知客户已经知道的信息?


1
这就是我的想法,如果无效,则可以返回验证消息,但是如果有效并且被创建/更新,则检查HTTP状态代码,并根据该消息向用户显示“ Hooray”消息
iswinky

3
有关/的信息,请参见stackoverflow.com/a/827045/290182,以避免混淆jQuery等。200204 No Content
beldaz

10

@iswinky在POST和PUT的情况下,我总是会发回有效负载。

如果是POST,则可以使用内部ID或UUID创建实体。因此,有意义的是发回有效载荷。

类似地,在PUT的情况下,您可能会忽略用户的某些字段(例如,不可变值),或者在PATCH的情况下,其他用户也可能已更改了数据。

将数据持久保存回去始终是一个好主意,并且绝对没有害处。如果呼叫者不需要此返回的数据,则他/她将不会处理该数据,而只会使用statusCode。否则,他们可以使用此数据来更新UI。

只有在DELETE的情况下,我才不会发回有效负载,并且会执行200响应内容或204响应内容。

编辑:由于下面的一些评论,我改写我的答案。我仍然坚持设计API并发送回响应的方式,但是我认为有必要限定我的一些设计思想。

a)当我说发回有效负载时,我实际上是说发回资源的数据,而不是请求中包含的有效负载。例如:如果您发送创建有效载荷,则我可能会在后端创建其他实体,例如UUID和(也许)时间戳以及某些(图形)连接。我会将所有这些都发送回响应中(不仅仅是请求有效负载,这是没有意义的)。

b)如果有效载荷很大,我不会发送回响应。我已经在评论中讨论了这一点,但是我想说明的是,我将尽力设计API或资源,以使其不必具有非常大的有效负载。我会尝试将资源分解为较小的可管理实体,以使每个资源都由15-20个JSON属性而不是更大的JSON属性定义。

如果对象很大或正在更新父对象,那么我会将嵌套结构作为href发送回去。

最重要的是,我绝对会尝试将对消费者/ UI有意义的数据发送回去,以便立即处理并通过原子API操作完成,而不必去获取2-5个以上的API只是为了在更新UI之后创建/更新数据。

服务器到服务器的API可能对此有所不同。我专注于可带来用户体验的API。


我看到很多情况,当有效载荷很大时,将整个有效载荷发回是个坏主意。
beldaz

2
@beldaz完全同意。YMMV基于REST API的设计。通常,我避免使用非常大的对象,并将其分解为一系列子资源/ PUT。如果有效载荷非常大,则有更好的方法来执行此操作,并且您将需要执行HATEOAS(如上面的Marjan所述),在该方法中,您将引用返回给对象,而不是对象本身。
ksprashu

@ksprashu:“因此,将有效负载发送回去是很有意义的”-我发现这是一个坏主意,因为这样可以通过多种方式获得资源:通过GET作为POST的响应,作为PUT的响应。这意味着客户端获得3种具有潜在不同结构的资源。好像您将只返回URI(位置)而没有正文一样,那么获取资源的唯一方法就是GET。这样可以确保客户端始终获得一致的响应。
mentallurg

@mentallurg我知道我可能没有措辞这项权利。感谢您指出。我已经编辑了答案。让我知道这是否更有意义。
ksprashu

只要您为家庭工作实施一项服务,就没有关系。随心所欲。节省您的时间。
mentallurg

9

参考链接RFC标准,使用Post成功存储请求资源时,您应该返回201(创建)状态。在大多数应用程序中,资源ID是由服务器本身生成的,因此优良作法是返回创建的资源的ID。返回整个对象是Post请求的开销。理想的做法是返回新创建资源的URL位置

例如,您可以参考以下示例,该示例将Employee对象保存到数据库中并返回新创建的资源对象的URL作为响应。

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

该其余端点将以以下形式返回响应:

状态201-已建立

标头位置→ http:// localhost:8080 / employees / 1


干净整洁-从现在开始及以后都将遵循此
规范

0

我会将返回正文中的有效负载设为HTTP参数的条件。

通常,最好将某种内容返回给API使用者,以避免不必要的往返行程(存在GraphQL的原因之一)。

实际上,随着我们的应用程序变得更加数据密集和分布,我尝试遵循以下准则:

我的指导方针

任何时候,在用例中,在POST或PUT之后立即需要GET的情况下,最好仅在POST / PUT响应中返回某些内容。

如何完成此操作以及从PUT或POST返回哪种类型的内容,这取决于应用程序。现在,如果应用程序可以在响应正文中参数化“内容”的类型,那将很有趣(我们是否只需要新对象的位置,某些字段或整个对象等)。

应用程序可以定义POST / PUT可以接收的一组参数,以控制要在响应正文中返回的“内容”的类型。或者它可以将某种GraphQL查询编码为参数(我看到这很有用,但也成为维护的噩梦。)

无论哪种方式,在我看来:

  1. 在POST / PUT响应主体中返回某些内容是可以的(并且很可能是可取的)。
  2. 如何完成此操作是针对特定应用程序的,几乎不可能一概而论。
  3. 您不希望默认返回大的“上下文”(交通噪声使您摆脱了POST后继的GET的全部原因。)

因此,1)做到这一点,但2)保持简单。

我见过的另一个选择是人们创建替代端点(例如,用于POST / PUT的/ customers在主体中不返回任何内容,而对于POST / PUT的/ customer_with_details向/ customers返回,但在响应主体中返回某些内容。)

不过,我会避免这种方法。当您合法地需要返回其他类型的内容时会发生什么?每种内容类型一个端点?无法扩展或维护。

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.