HTTP PUT vs HTTP PATCH dans une API REST

1. Vue d'ensemble

Dans cet article rapide, nous examinons les différences entre les verbes HTTP PUT et PATCH et la sémantique des deux opérations.

Nous utiliserons Spring pour implémenter deux points de terminaison REST qui prennent en charge ces deux types d'opérations et pour mieux comprendre les différences et la bonne façon de les utiliser.

2. Quand utiliser Put et quand Patch?

Commençons par une déclaration simple et légèrement simple.

Lorsqu'un client a besoin de remplacer entièrement une ressource existante, il peut utiliser PUT. Lorsqu'ils font une mise à jour partielle, ils peuvent utiliser HTTP PATCH.

Par exemple, lors de la mise à jour d'un seul champ de la ressource, l'envoi de la représentation complète de la ressource peut être fastidieux et utilise beaucoup de bande passante inutile. Dans de tels cas, la sémantique de PATCH a beaucoup plus de sens.

Un autre aspect important à considérer ici est l' idempotence; PUT est idempotent; PATCH peut l'être, mais ce n'est pas obligatoire . Et, donc - en fonction de la sémantique de l'opération que nous implémentons, nous pouvons également choisir l'un ou l'autre en fonction de cette caractéristique.

3. Implémentation de la logique PUT et PATCH

Disons que nous voulons implémenter l'API REST pour mettre à jour un HeavyResource avec plusieurs champs:

public class HeavyResource { private Integer id; private String name; private String address; // ...

Tout d'abord, nous devons créer le point de terminaison qui gère une mise à jour complète de la ressource à l'aide de PUT:

@PutMapping("/heavyresource/{id}") public ResponseEntity saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved"); }

Il s'agit d'un point de terminaison standard pour la mise à jour des ressources.

Maintenant, disons que le champ d'adresse sera souvent mis à jour par le client. Dans ce cas, nous ne voulons pas envoyer tout l' objet HeavyResource avec tous les champs , mais nous voulons la possibilité de mettre à jour uniquement le champ d' adresse - via la méthode PATCH.

Nous pouvons créer un DTO HeavyResourceAddressOnly pour représenter une mise à jour partielle du champ d'adresse:

public class HeavyResourceAddressOnly { private Integer id; private String address; // ... }

Ensuite, nous pouvons utiliser la méthode PATCH pour envoyer une mise à jour partielle:

@PatchMapping("/heavyresource/{id}") public ResponseEntity partialUpdateName( @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) { heavyResourceRepository.save(partialUpdate, id); return ResponseEntity.ok("resource address updated"); }

Avec ce DTO plus granulaire, nous pouvons envoyer le champ que nous devons mettre à jour uniquement - sans la surcharge liée à l' envoi de toute la ressource HeavyResource .

Si nous avons un grand nombre de ces opérations de mise à jour partielle, nous pouvons également ignorer la création d'un DTO personnalisé pour chaque sortie - et n'utiliser qu'une carte:

@RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity partialUpdateGeneric( @RequestBody Map updates, @PathVariable("id") String id) { heavyResourceRepository.save(updates, id); return ResponseEntity.ok("resource updated"); }

Cette solution nous donnera plus de flexibilité dans la mise en œuvre de l'API; cependant, nous perdons également quelques éléments - comme la validation.

4. Tester PUT et PATCH

Enfin, écrivons des tests pour les deux méthodes HTTP. Tout d'abord, nous voulons tester la mise à jour de la ressource complète via la méthode PUT:

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

L'exécution d'une mise à jour partielle est réalisée en utilisant la méthode PATCH:

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

Nous pouvons également écrire un test pour une approche plus générique:

HashMap 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. Gestion des demandes partielles avec des valeurs nulles

Lorsque nous écrivons une implémentation pour une méthode PATCH, nous devons spécifier un contrat sur la façon de traiter les cas lorsque nous obtenons null comme valeur pour le champ d' adresse dans HeavyResourceAddressOnly.

Supposons que le client envoie la demande suivante:

{ "id" : 1, "address" : null }

Ensuite, nous pouvons gérer cela en définissant une valeur du champ d' adresse sur null ou en ignorant simplement une telle demande en la traitant comme sans changement.

Nous devrions choisir une stratégie pour gérer NULL et nous y tenir dans chaque implémentation de méthode PATCH.

6. Conclusion

Dans ce rapide tutoriel, nous nous sommes concentrés sur la compréhension des différences entre les méthodes HTTP PATCH et PUT.

Nous avons implémenté un simple contrôleur Spring REST pour mettre à jour une ressource via la méthode PUT et une mise à jour partielle à l'aide de PATCH.

L'implémentation de tous ces exemples et extraits de code se trouve dans le projet GitHub - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.