HTTP PUT vs HTTP PATCH dans une API REST

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 prenant 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 effectuent 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 s'avérer fastidieux et utiliser beaucoup de bande passante inutile. Dans ce cas, la sémantique de PATCH est beaucoup plus logique.

Un autre aspect important à considérer ici estidempotence; PUT is idempotent; PATCH can be, but isn’t required to. Et donc, en fonction de 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.

3. Implémentation de la logique PUT et PATCH

Supposons que nous souhaitons implémenter l'API REST pour mettre à jour unHeavyResource avec plusieurs champs:

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

Tout d'abord, nous devons créer le noeud final 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 noeud final standard pour la mise à jour des ressources.

Maintenant, disons que le champ d'adresse sera souvent mis à jour par le client. Dans ce cas,we don’t want to send the whole HeavyResource object with all fields, mais nous voulons la possibilité de mettre à jour uniquement le champaddress - via la méthode PATCH.

On peut créer un DTOHeavyResourceAddressOnly 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 de l'envoi deHeavyResource entiers.

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 utiliser uniquement 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 des API; Cependant, nous perdons également quelques éléments, tels que la validation.

4. Tester PUT et PATCH

Enfin, écrivons des tests pour les deux méthodes HTTP. Premièrement, 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 à l’aide de 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 valeursNull

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 obtenonsnull comme valeur pour le champaddress dans leHeavyResourceAddressOnly.

Supposons que le client envoie la requête suivante:

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

Ensuite, nous pouvons gérer cela en définissant une valeur du champaddress surnull ou en ignorant simplement une telle demande en la traitant comme sans changement.

Nous devrions choisir une stratégie pour gérer lesnull 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 mis en place un simple contrôleur Spring REST pour mettre à jour une méthode Resource via PUT et une mise à jour partielle à l'aide de PATCH.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.