REST API 允许根据资源状态更新资源

REST API allow update of resource depending on state of resource

我最近阅读了关于在 Spring 引导中实现 RESTful API 的指南,来自官方 Spring.io 教程网站(link 到教程: 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 方法是正确的阶段?基本上是一个 update/patch 将使它自己的先决条件无效。

something in the guide seemed to contradict my understanding of how REST API's should be built. I am now wondering if my understanding is wrong or if the guide is not of as high a quality as I expected it to be.

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


From what I read at https://restfulapi.net/rest-put-vs-post/ a PUT method should be idempotent; meaning that you should be able to call it multiple times in a row without it causing problems. However, in this implementation only the first PUT request would have an effect and all further PUT requests to the same resource would result in an error message.

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

A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.

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

PUT /orders/12345/complete

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

HTTP does not define exactly how a PUT method affects the state of an origin server beyond what can be expressed by the intent of the user agent request and the semantics of the origin server response. It does not define what a resource might be, in any sense of that word, beyond the interface provided via HTTP. It does not define how resource state is "stored", nor how such storage might change as a result of a change in resource state, nor how the origin server translates resource state into representations. Generally speaking, all implementation details behind the resource interface are intentionally hidden by the server. -- RFC 7231

所以在他们的 CURL 示例中

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

这条消息的意思是“用一个空的表示替换/orders/4/complete的当前表示”。但是原始服务器可以选择如何执行此操作,以及对客户端 return 的标准化响应。

所以这是很好

All work is transacted by politely placing documents in in-trays, and then some side effect of placing that document in an in-tray causes some business activity to occur -- Jim Webber, 2011.

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


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

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

The DELETE method requests that the origin server remove the association between the target resource and its current functionality. In effect, this method is similar to the rm command in UNIX: it expresses a deletion operation on the URI mapping of the origin server rather than an expectation that the previously associated information be deleted.

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

Relatively few resources allow the DELETE method -- its primary use is for remote authoring environments, where the user has some direction regarding its effect.

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

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


what HTTP method is right to use when you want to advance the status of a resource to the next stage without giving an option of going back to an earlier stage? Basically an update/patch that will invalidate its own pre-conditions.

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