在RESTful服务中的非CRUD操作

109

"RESTful"方式如何向RESTful服务添加非CRUD操作?假设我有一个允许对记录进行CRUD访问的服务,类似于这样:

GET /api/car/123           <- Returns information for the Car object with ID 123
POST /api/car              <- Creates a new car (with properties in the request)
PUT /api/car/123           <- Updates car 123 (with properties in the request)
DELETE /api/car/123        <- Deletes car 123    
POST /api/car/123/wheel/   <- Creates a wheel and associates it to car 123
如果我想改变汽车的颜色,我只需要POST /api/car/123并包含一个新颜色的POST变量。
但是假设我想购买一辆车,而这个操作比简单更新“用户”记录的“拥有的车辆”属性要复杂得多。那么是否以这种方式POST /api/car/123/purchase符合RESTful规范,其中“purchase”实际上是一个方法名称?还是应该使用自定义HTTP动词,例如使用PURCHASE代替POST
或者非CRUD操作完全超出了REST的范畴?

6
如果要改变汽车的颜色,最好使用 PATCH /api/car/123 并发送一个颜色参数,或者使用 PUT /api/car/123 并发送整个汽车对象。使用POST会暗示你正在创建一辆新汽车,并且通常不应该在URL末尾包含ID。 - RonnyKnoxville
4个回答

68

购买视为RESTful字典中的业务实体或资源。也就是说,进行购买实际上是创建一个新的资源。因此:

POST /api/purchase

将会下订单。发送到此地址的内容应该通过id(或URI)引用详细信息(用户、汽车等)。

重要的是,订购汽车并不仅仅是在数据库中进行简单的INSERT操作。实际上,REST并不是将数据库表作为CRUD操作公开的。从逻辑角度来看,您正在创建一个订单(购买),但服务器端可以自由地执行尽可能多的处理步骤。

您甚至可以进一步滥用HTTP协议。使用Location头部返回一个指向新建订单的链接,精心选择HTTP响应代码以通知用户有关问题(服务器端或客户端),等等。


3
REST的核心是操作资源的状态,每个业务操作都必须映射到状态CRUD操作。如果您需要“硬”业务操作语义,您将不得不采用SOAP方式(SOAP实际上是消息传递,但通常以请求-响应操作组织)。 - Tomasz Nurkiewicz
24
“购买作为资源”的设计看起来很不错。如果资源是一瓶啤酒,而我想让服务器喝掉它(如果是我的话,我肯定会这么做 ;))...那我们是否应该将“喝”动作视为资源?!或者说,“喝一瓶啤酒”是一个非常困难的业务操作?更严肃地说,RESTful设计是否考虑将动作视为资源?! - Myobis
2
你如何通过REST服务公开“批准采购订单”?我认为@TomaszNurkiewicz是正确的,任何无法以CRUD方式整洁完成的操作都需要SOAP提供的操作语义。除非“采购订单批准”是一个独立的模型/实体。例如:POST /po-approval(请求中包含PO详细信息)。 - mydoghasworms
3
从REST客户端的角度来看,"批准采购订单"应该只是订单的另一个更新操作。例如,将"已批准"更改为"true"并将更新发送到服务器。服务器可能需要进行一系列检查,可能需要更新/创建其他资源。但这是服务器的问题,不应当对客户端可见。 - AVee
2
如果客户端知道某些内容,你不能说你在用REST(不过这仍然可以是有效的且合理的软件!)。REST 被设计成能够创建那些并不知道此类信息、并且依然能够正常工作的客户端,以应对服务器行为变更的情况。你试图做的是经典的 RPC,要么你需要重新审视自己的方法以适应 REST,要么你必须接受正在执行 RPC 的事实,并使用一种针对 RPC 的协议,如 SOAP。REST 致力于避免成为RPC,因此当你想/需要 RPC 时,它永远不会是一个好选择。 - AVee
显示剩余5条评论

15

据我理解,RESTful 的方式是,你不需要新的 HTTP 动词,而是在某个地方会有一个名词表示你想要做的事情。

购买一辆汽车?那不就是

POST /api/order

4
因为PUT是幂等的,所以它被用来更新资源,这意味着你可以调用它很多次,但只有第一次/最后一次调用是重要的。另一方面,POST被用来创建资源,调用两次应该会创建两个资源。 - Tomasz Nurkiewicz
1
@Tomas,是的,打错了。但原则很重要,我们正在处理一件新事物——一个订单,没有必要新建一个动词。 - djna

5

你真正要做的是创建一个订单。因此,在订单过程中添加另一个资源以进行订单和发布。

以资源而非方法调用的方式思考。

为了完成订单,你可能需要进行POST /api/order//complete或类似的操作。


3
我认为REST API的帮助远不止提供语义这么简单。因此,不能仅仅因为某些呼叫看起来更适合RPC操作方式就选择RPC风格。例如谷歌地图API是用于查找两个地点之间的方向,其表现形式如下: http://maps.googleapis.com/maps/api/directions/json?origin=Jakkur&destination=Hebbal 他们本可以把它称作“findDirections”(动词)并将其视为一个操作。相反地,他们让“direction”(名词)成为一种资源,并将寻找方向视为对方向资源的查询(尽管在内部可能没有真正的名为direction的资源,它可能是通过业务逻辑根据参数查找方向实现的)。

1
这是一个糟糕的例子。在这种情况下,方向(所有可能的方向,无限数量)是资源,而参数只是过滤器。但是你不能用这个来进行“购买”,因为过滤器只对获取操作有意义,而下订单或取消订单是改变数据的操作。 - Tseng
2
购买将被POST到/order,其中包含一个JSON表示订单已创建。 取消将被PUT到/order,其中携带订单状态更改的JSON以指示这是幂等更新。 我还没有遇到无法用资源格式表达的操作。所以很想看看这样的例子。 - Maruthi

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