REST API中的HTTP PUT与HTTP PATCH

HTTP PUT vs HTTP PATCH in a REST API

1.概述

在这篇快速文章中,我们将研究HTTP PUT和PATCH动词之间的差异以及这两个操作的语义。

我们将使用Spring来实现支持这两种类型的操作的两个REST端点,并更好地理解它们之间的差异以及正确的使用方式。

2.何时使用看跌期权和何时打补丁?

让我们从一个简单的,稍微简单的语句开始。

当客户端需要完全替换现有资源时,他们可以使用PUT。当他们进行部分更新时,他们可以使用HTTP PATCH。

例如,当更新"资源"的单个字段时,发送完整的"资源"表示可能很麻烦,并且会占用大量不必要的带宽。在这种情况下,PATCH的语义更加有意义。

这里要考虑的另一个重要方面是幂等。 PUT是幂等的;修补程序可以但不是必需的。因此,根据所执行操作的语义,我们还可以根据此特征选择一个或另一个。

3.实现PUT和PATCH逻辑

假设我们要实现REST API,以更新具有多个字段的HeavyResource:

1
2
3
4
5
public class HeavyResource {
    private Integer id;
    private String name;
    private String address;
    // ...

首先,我们需要创建一个端点,该端点使用PUT处理资源的完整更新:

1
2
3
4
5
6
@PutMapping("/heavyresource/{id}")
public ResponseEntity< ? > saveResource(@RequestBody HeavyResource heavyResource,
  @PathVariable("id") String id) {
    heavyResourceRepository.save(heavyResource, id);
    return ResponseEntity.ok("resource saved");
}

这是用于更新资源的标准端点。

现在,让我们说说地址字段通常会由客户端更新。在那种情况下,我们不希望发送带有所有字段的整个HeavyResource对象,但是我们希望能够通过PATCH方法仅更新地址字段。

我们可以创建HeavyResourceAddressOnly DTO来表示地址字段的部分更新:

1
2
3
4
5
6
public class HeavyResourceAddressOnly {
    private Integer id;
    private String address;
   
    // ...
}

接下来,我们可以利用PATCH方法发送部分更新:

1
2
3
4
5
6
7
@PatchMapping("/heavyresource/{id}")
public ResponseEntity< ? > partialUpdateName(
  @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) {
   
    heavyResourceRepository.save(partialUpdate, id);
    return ResponseEntity.ok("resource address updated");
}

使用这种更精细的DTO,我们可以发送仅需要更新的字段,而无需发送整个HeavyResource的开销。

如果我们有大量的部分更新操作,我们还可以跳过为每个输出创建自定义DTO的操作-仅使用映射:

1
2
3
4
5
6
7
8
@RequestMapping(value ="/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity< ? > partialUpdateGeneric(
  @RequestBody Map<String, Object> updates,
  @PathVariable("id") String id) {
   
    heavyResourceRepository.save(updates, id);
    return ResponseEntity.ok("resource updated");
}

该解决方案将使我们在实现API方面更具灵活性。但是,我们也确实会丢失一些东西,例如验证。

4.测试PUT和PATCH

最后,让我们为这两种HTTP方法编写测试。首先,我们要通过PUT方法测试完整资源的更新:

1
2
3
4
5
mockMvc.perform(put("/heavyresource/1")
  .contentType(MediaType.APPLICATION_JSON_VALUE)
  .content(objectMapper.writeValueAsString(
    new HeavyResource(1,"Tom","Jackson", 12,"heaven street")))
  ).andExpect(status().isOk());

通过使用PATCH方法可以执行部分??更新:

1
2
3
4
5
mockMvc.perform(patch("/heavyrecource/1")
  .contentType(MediaType.APPLICATION_JSON_VALUE)
  .content(objectMapper.writeValueAsString(
    new HeavyResourceAddressOnly(1,"5th avenue")))
  ).andExpect(status().isOk());

我们还可以为更通用的方法编写测试:

1
2
3
4
5
6
7
HashMap<String, Object> updates = new HashMap<>();
updates.put("address","5th avenue");

mockMvc.perform(patch("/heavyresource/1")
    .contentType(MediaType.APPLICATION_JSON_VALUE)
    .content(objectMapper.writeValueAsString(updates))
  ).andExpect(status().isOk());

5.处理具有空值的部分请求

在编写PATCH方法的实现时,我们需要指定一个协定,规定如何在HeavyResourceAddressOnly中的地址字段的值为null时处理大小写。

假设客户端发送以下请求:

1
2
3
4
{
  "id" : 1,
  "address" : null
}

然后,我们可以通过将地址字段的值设置为null或通过将其视为不变来忽略此类请求来进行处理。

我们应该选择一种处理null的策略,并在每个PATCH方法实现中都坚持使用它。

六,结论

在本快速教程中,我们专注于理解HTTP PATCH和PUT方法之间的差异。

我们实现了一个简单的Spring REST控制器,以通过PUT方法更新资源,并使用PATCH进行部分更新。

所有这些示例和代码段的实现都可以在GitHub项目中找到–这是一个Maven项目,因此应该很容易直接导入和运行。