Overview
Dans ce rapide article, nous allons examiner 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 endpoints REST qui supportent ces deux types d’opérations, et pour mieux comprendre les différences et la bonne façon de les utiliser.
Quand utiliser Put et quand Patch ?
Débutons par une déclaration simple, et un peu simple.
Lorsqu’un client a besoin de remplacer entièrement une ressource existante, il peut utiliser PUT. Lorsqu’il effectue une mise à jour partielle, il peut 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 pourrait être encombrant et utiliser 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 n’est pas obligé de l’être. Et, donc – selon la sémantique de l’opération que nous mettons en œuvre, nous pouvons également choisir l’un ou l’autre en fonction de cette caractéristique.
Mise en œuvre de la logique PUT et PATCH
Disons que nous voulons mettre en œuvre l’API REST pour mettre à jour une HeavyResource avec plusieurs champs :
public class HeavyResource { private Integer id; private String name; private String address; // ...
D’abord, nous devons créer le point de terminaison qui gère une mise à jour complète de la ressource en utilisant PUT :
@PutMapping("/heavyresource/{id}")public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource, @PathVariable("id") String id) { heavyResourceRepository.save(heavyResource, id); return ResponseEntity.ok("resource saved");}
C’est 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 l’objet HeavyResource entier 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; // ...}
Puis, nous pouvons tirer parti de 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 l’overhead de l’envoi de la HeavyResource entière.
Si nous avons un grand nombre de ces opérations de mise à jour partielle, nous pouvons également sauter la création d’un DTO personnalisé pour chaque sortie – et seulement utiliser une carte :
@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");}
Cette solution nous donnera plus de flexibilité dans l’implémentation de l’API ; cependant, nous perdons aussi quelques choses – comme la validation.
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<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());
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 de la façon de traiter les cas où nous obtenons null comme valeur pour le champ adresse dans le HeavyResourceAddressOnly.
Supposons que le client envoie la requête suivante :
{ "id" : 1, "address" : null}
Alors nous pouvons traiter cela comme mettant une valeur du champ d’adresse à null ou simplement ignorer une telle requête en la traitant comme no-change.
Nous devrions choisir une stratégie pour traiter null et nous y tenir dans chaque mise en œuvre de la méthode PATCH.
Conclusion
Dans ce rapide tutoriel, nous nous sommes attachés à comprendre les différences entre les méthodes HTTP PATCH et PUT.
Nous avons implémenté un contrôleur REST Spring simple pour mettre à jour une ressource via la méthode PUT et une mise à jour partielle en utilisant PATCH.
L’implémentation de tous ces exemples et extraits de code peut être trouvée dans le projet GitHub – c’est un projet Maven, donc il devrait être facile à importer et à exécuter tel quel.
Débutez avec Spring 5 et Spring Boot 2, grâce au cours Learn Spring :
>> VÉRIFIER LE COURS
.