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 |
首先,我们需要创建一个端点,该端点使用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来表示地址字段的部分更新:
接下来,我们可以利用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项目,因此应该很容易直接导入和运行。