同一个URI上的多个操作时如何使用RESTful API

26
据我所知,RESTful API 中通常使用四种方法:
使用GET来获取资源。
使用POST来更新资源。
使用PUT来创建或替换资源。
使用DELETE来删除资源。
假设我们有一个名为“apple”的资源,可以通过多种方式进行“更新”,例如削皮、切片或制作苹果汁。每个不同的更新操作都需要不同的参数,在它们的API中,共同的部分将是:
POST /apple HTTP/1.1
Host: www.example.com

<different combination of arguments>
在这种情况下,三个API共享相同的URI和相同的请求方法,它们唯一的区别是参数。我认为这迫使后端准备接受这些参数的联合集,并且为了区分实际请求的操作,后端需要检查参数的组合。这太过复杂而不优雅。
所以我的问题是: 在这个场景中,如何设计一个优雅的RESTful API集,让后端能够轻松处理它?
4个回答

19
首先,尽量避免将HTTP方法与CRUD操作混淆。我认为这是REST中混淆的主要原因。像这样直接将HTTP方法转换为CRUD操作并不干净利落。我在这里有一个详细的答案:S3 REST API 和 POST 方法
简而言之:
- POST 是用于任何未被标准化的 HTTP 操作的方法,并将有效载荷提交到目标URI。 - PUT 用于完全替换当前 URI 上的资源,并将有效载荷提交给服务本身。 - PATCH 用于进行部分幂等更新,具有当前状态和期望状态之间的差异。 - DELETE 用于删除资源。 - GET 用于检索资源。
在后端方面,试着将REST资源更多地视为状态机,您可以使用方法来强制进行状态转换,而不是将其视为具有方法的对象。这样,您就可以将实现重点放在资源本身上,而不是协议交互上。例如,您可以直接从方法负载更改对象的属性,然后调用一个方法来检测需要的转换。
例如,您可以将苹果视为具有三个状态:完整、削皮、切片和榨汁。您可以通过使用方法的标准行为在状态之间进行转换。
GET /apple 

{"state": "whole",
 "self": "/apple"}

然后您想对它进行切片。您可以这样做:

PUT /apple

{"state": "sliced"}

或者你可以做类似于这样的事情:

PATCH /apple

{"from_state": "whole", "to_state": "sliced"}

或者甚至是这样的:

POST /apple

{"transition": "slice"}

这个想法是实现可以足够通用,以至于你不必太担心将资源与HTTP方法耦合在一起。

  • PUT版本是幂等的,因此客户端可以选择在需要幂等性时使用它。
  • PATCH版本保证客户端了解当前状态并尝试进行有效的转换。
  • POST版本最灵活,您可以随意使用,但需要详细记录。不能简单地假设您的客户端知道该方法的工作方式。

只要您对资源的实现理解到当apple.state被更改为其他内容时,它应检测发生了什么更改并执行适当的转换,那么您就完全脱离了协议。所使用的方法并不重要。

我认为这是最优雅的解决方案,使后端处理变得更加容易。您可以实现对象而不必太担心协议。只要对象之间可以转换状态,它们就可以被任何能够影响这些转换的协议使用。


PUT /apple/color newColor=red,或者 PUT /apple cmd=updateColor&newColor=red,这两个API的URI和参数组合,哪一个更好(或更符合RESTful风格)? - dastan
6
其中任何一种都不符合RESTful的标准。第一种需要整个有效负载作为颜色资源的表示,而第二种则意味着updateColor命令由苹果资源本身执行。如果使用"PUT /apple/color"请求,将'red'作为整个有效负载并采用苹果颜色属性的媒体类型,则可以认为它符合RESTful标准,但第二种选项已无法修复。 - Pedro Werneck

6
我的RESTful HTTP API与你的有所不同。我有: < strong >GET< /strong >用于获取资源。< br /> < strong >POST< /strong >用于将新资源添加到集合中。< br /> < strong >PUT< /strong >用于替换资源(包括截断集合)。< br /> < strong >DELETE< /strong >用于删除资源。< br /> < strong >PATCH< /strong >用于更新资源。< br /> < strong >LINK< /strong >表示两个资源之间的关系。< br /> < strong >UNLINK< /strong >用于删除两个资源之间的关系。< /p > “叶”资源也可以看作是一个集合。
例如,假设您有 /fruits并且您将< code >apple< /code >发布到该集合资源,则返回:
201 Created
Location: /fruits/apple

同样地,您可以将/fruits/apple视为其属性的集合,如下所示:
GET /fruits/apple
->
colour=red&diameter=47mm

GET /fruits/apple/colour
->
red

GET /fruits/apple/diameter
->
47mm

因此:

PUT /fruits/apple/slices
"12"
->
201 Created

GET /fruits/apple
->
colour=red&diameter=47mm&slices=12

总结一下,我建议将你的操作表示为名词,并将这些名词定位为要应用操作的资源的子资源。


1

从资源的角度思考。这里的苹果是一种资源。

要向列表“/apples”添加一个或多个苹果,请使用POST。REST风格允许发布数组。

POST /apples HTTP/1.1
Host: www.example.com

现在假设您有一个ID为123的苹果。您可以使用GET方法在“/apple/123”上获取详细信息。
GET /apples/123 HTTP/1.1
Host: www.example.com

要对苹果123进行任何更改,只需直接进行POST即可。

PUT /apples/123 HTTP/1.1
Host: www.example.com

削皮、切片,或者榨成苹果汁——这些都是基本上改变苹果 123 的一些属性。正如你所说(正确地),使用不同的属性组合更新。

6
@DavidBrabant,使用POST进行更新并不是“完全错误”的。POST可以用于任何未被其他方法标准化的操作。PUT必须是幂等更新,因此如果您想要进行非幂等更新,则应使用POST方法。 - Pedro Werneck

0

我认为这取决于实现者的决定,但我看到有两种方法。从单一职责的角度来看,提供不同的服务可能是有意义的。

然而,如果你坚持使用一个服务,那么你可以传递一个带有动作类型限定符的对象,以便轻松地将请求委托给服务中的不同代码。然后,单个对象可以具有其他可选参数,以支持每个操作的数据需求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接