【问题标题】:REST API allow update of resource depending on state of resourceREST API 允许根据资源状态更新资源
【发布时间】:2021-09-27 12:51:59
【问题描述】:

我最近阅读了 Spring.io 官方教程网站上关于在 Spring Boot 中实现 RESTful API 的指南(教程链接:https://spring.io/guides/tutorials/rest/

但是,指南中的某些内容似乎与我对如何构建 REST API 的理解相矛盾。我现在想知道是我的理解有误,还是该指南的质量没有我预期的那么高。

我的问题是这个 PUT 方法的实现来更新订单状态:

@PutMapping("/orders/{id}/complete")
ResponseEntity<?> complete(@PathVariable Long id) {

  Order order = orderRepository.findById(id) //
      .orElseThrow(() -> new OrderNotFoundException(id));

  if (order.getStatus() == Status.IN_PROGRESS) {
    order.setStatus(Status.COMPLETED);
    return ResponseEntity.ok(assembler.toModel(orderRepository.save(order)));
  }

  return ResponseEntity //
      .status(HttpStatus.METHOD_NOT_ALLOWED) //
      .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE) //
      .body(Problem.create() //
          .withTitle("Method not allowed") //
          .withDetail("You can't complete an order that is in the " + order.getStatus() + " status"));
}

从我在https://restfulapi.net/rest-put-vs-post/ 阅读的内容来看,PUT 方法应该是幂等的;这意味着您应该能够连续多次调用它而不会引起问题。但是,在此实现中,只有第一个 PUT 请求会产生影响,并且对同一资源的所有进一步 PUT 请求都会导致错误消息。

根据 RESTful API 可以吗?如果没有,什么是更好的使用方法?我不认为 POST 会更好。

此外,在同一指南中,他们以类似的方式使用 DELETE 方法将订单状态更改为取消:

@DeleteMapping("/orders/{id}/cancel")
ResponseEntity<?> cancel(@PathVariable Long id) {

  Order order = orderRepository.findById(id) //
      .orElseThrow(() -> new OrderNotFoundException(id));

  if (order.getStatus() == Status.IN_PROGRESS) {
    order.setStatus(Status.CANCELLED);
    return ResponseEntity.ok(assembler.toModel(orderRepository.save(order)));
  }

  return ResponseEntity //
      .status(HttpStatus.METHOD_NOT_ALLOWED) //
      .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE) //
      .body(Problem.create() //
          .withTitle("Method not allowed") //
          .withDetail("You can't cancel an order that is in the " + order.getStatus() + " status"));
}

这对我来说看起来很不对劲。我们这里没有删除任何东西,它与前面的 PUT 方法基本相同,只是我们想要移动到不同的状态。我认为本教程的这一部分是伪造的是否正确?

TL;DR:当您想将资源的状态推进到下一阶段而不提供返回到较早阶段的选项时,使用哪种 HTTP 方法是正确的?基本上是一个更新/补丁,它会使自己的先决条件无效。

【问题讨论】:

  • 技术上它是幂等的。无论您PUT多少次/完成它只会完成一次,当订单状态为Status.IN_PROGRESS时。当您尝试完成已完成的订单时,所有其他调用将导致 HttpStatus.METHOD_NOT_ALLOWED。删除也一样。您只能删除一次。幂等性在服务器端。

标签: rest put restful-url


【解决方案1】:

指南中的某些内容似乎与我对如何构建 REST API 的理解相矛盾。我现在想知道是我的理解有误,还是该指南的质量没有我预期的那么高。

我不认为本指南是可靠的权威 - 所描述的资源模型有一些非常值得怀疑的选择。


从我在https://restfulapi.net/rest-put-vs-post/ 看到的内容来看,PUT 方法应该是幂等的;这意味着您应该能够连续多次调用它而不会引起问题。但是,在此实现中,只有第一个 PUT 请求会产生影响,并且对同一资源的所有进一步 PUT 请求都会导致错误消息。

HTTP中幂等语义的权威定义目前为RFC 7231

如果使用该方法的多个相同请求对服务器的预期效果与单个此类请求的效果相同,则该请求方法被认为是“幂等的”。

注意:“效果”,而不是“响应”。

PUT /orders/12345/complete

表示“请将 /orders/12345/complete 的当前表示替换为有效负载中的表示”。换句话说,“将此文件保存在当前副本之上”。将同一个文件连续保存两三次与保存一次的效果是一样的,这就是“幂等的”。

HTTP 没有准确定义 PUT 方法如何影响源服务器的状态,超出了用户代理请求的意图和源服务器响应的语义所能表达的范围。除了通过 HTTP 提供的接口,它没有定义资源可能是什么,从这个词的任何意义上讲。它没有定义如何“存储”资源状态,也没有定义这种存储如何由于资源状态的变化而改变,也没有定义源服务器如何将资源状态转换为表示形式。一般来说,资源接口背后的所有实现细节都被服务器有意隐藏。 -- RFC 7231

所以在他们的 CURL 示例中

PUT /orders/4/complete HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.54.0
Accept: */*

此消息的含义是“将 /orders/4/complete 的当前表示替换为空表示”。但是源服务器可以选择如何执行此操作,以及将哪些标准化响应返回给客户端。

所以这很好

所有工作都是通过礼貌地将文档放入托盘中来处理的,然后将该文档放入托盘中的一些副作用会导致一些业务活动发生 -- Jim Webber, 2011

在这种情况下,我们放入“收件盘”的文档恰好是空白的。


@DeleteMapping("/orders/{id}/cancel")

我永远不会在代码审查中批准这个选择。 DELETE(如 PUT)在“通过网络域传输文档”具有语义。

DELETE 方法请求源服务器删除目标资源与其当前功能之间的关联。实际上,该方法类似于 UNIX 中的 rm 命令:它表示对源服务器的 URI 映射的删除操作,而不是期望删除之前关联的信息。

试图劫持方法,因为拼写有点像域操作,是在选择方法时使用的错误启发式方法。

相对较少的资源允许使用 DELETE 方法——它的主要用途是远程创作环境,用户对其效果有一定的指导。

关键是我们有一个通用的文档操作界面,我们将该界面用作外观,让我们能够推动业务活动。所以我们应该使用我们标准化的消息语义就像网络上所有其他页面一样

@PutMapping 是可以辩护的,使用与 /complete 相同的理由。


当您想将资源的状态推进到下一阶段而不提供返回到较早阶段的选项时,使用哪种 HTTP 方法是正确的?基本上是一个更新/补丁,它将使其自己的先决条件无效。

PUT、PATCH 和 POST 都是在编辑资源表示时使用的合适方法。当您发送资源的替换表示时使用 PUT 或 PATCH,当您要求服务器计算对表示的编辑应该是什么时使用 POST。

【讨论】:

    猜你喜欢
    • 2023-03-21
    • 2016-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    • 1970-01-01
    • 2018-06-03
    相关资源
    最近更新 更多