HTTP PUT vs. HTTP PATCH in einer REST-API

HTTP PUT vs HTTP PATCH in einer REST-API

1. Überblick

In diesem kurzen Artikel untersuchen wir die Unterschiede zwischen den Verben HTTP PUT und PATCH sowie die Semantik der beiden Operationen.

Wir werden Spring verwenden, um zwei REST-Endpunkte zu implementieren, die diese beiden Arten von Vorgängen unterstützen, und um die Unterschiede und die richtige Verwendung besser zu verstehen.

2. Wann Put und Wann Patch verwenden?

Beginnen wir mit einer einfachen und leicht einfachen Aussage.

Wenn ein Client eine vorhandene Ressource vollständig ersetzen muss, kann er PUT verwenden. Wenn sie eine teilweise Aktualisierung durchführen, können sie HTTP PATCH verwenden.

Wenn Sie beispielsweise ein einzelnes Feld der Ressource aktualisieren, ist das Senden der vollständigen Ressourcendarstellung möglicherweise umständlich und beansprucht viel unnötige Bandbreite. In solchen Fällen ist die Semantik von PATCH viel sinnvoller.

Ein weiterer wichtiger Aspekt, der hier berücksichtigt werden muss, istidempotence; PUT is idempotent; PATCH can be, but isn’t required to. Abhängig von der Semantik der Operation, die wir implementieren, können wir auch die eine oder andere basierend auf dieser Eigenschaft auswählen.

3. Implementierung von PUT und PATCH Logic

Angenommen, wir möchten die REST-API zum Aktualisieren vonHeavyResource mit mehreren Feldern implementieren:

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

Zunächst müssen wir den Endpunkt erstellen, der eine vollständige Aktualisierung der Ressource mithilfe von PUT ausführt:

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

Dies ist ein Standardendpunkt zum Aktualisieren von Ressourcen.

Angenommen, das Adressfeld wird häufig vom Client aktualisiert. In diesem Fallwe don’t want to send the whole HeavyResource object with all fields, aber wir möchten, dass nur das Feldaddress aktualisiert werden kann - über die PATCH-Methode.

Wir können einHeavyResourceAddressOnly DTO erstellen, um eine teilweise Aktualisierung des Adressfelds darzustellen:

public class HeavyResourceAddressOnly {
    private Integer id;
    private String address;

    // ...
}

Als nächstes können wir die PATCH-Methode nutzen, um ein teilweises Update zu senden:

@PatchMapping("/heavyresource/{id}")
public ResponseEntity partialUpdateName(
  @RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) {

    heavyResourceRepository.save(partialUpdate, id);
    return ResponseEntity.ok("resource address updated");
}

Mit diesem detaillierteren DTO können wir das Feld senden, das wir nur aktualisieren müssen - ohne den Aufwand, ganzeHeavyResource zu senden.

Wenn wir eine große Anzahl dieser Teilaktualisierungsvorgänge haben, können wir auch die Erstellung eines benutzerdefinierten DTO für jeden Ausgang überspringen - und nur eine Karte verwenden:

@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");
}

Diese Lösung bietet uns mehr Flexibilität bei der Implementierung der API. Wir verlieren jedoch auch einige Dinge - wie die Validierung.

4. Testen von PUT und PATCH

Lassen Sie uns abschließend Tests für beide HTTP-Methoden schreiben. Zunächst möchten wir die Aktualisierung der vollständigen Ressource über die PUT-Methode testen:

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

Die Ausführung einer Teilaktualisierung erfolgt mit der PATCH-Methode:

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

Wir können auch einen Test für einen allgemeineren Ansatz schreiben:

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. Teilanfragen mitNull Werten bearbeiten

Wenn wir eine Implementierung für eine PATCH-Methode schreiben, müssen wir einen Vertrag zur Behandlung von Fällen angeben, in denennull als Wert für das Feldaddress inHeavyResourceAddressOnly. erhalten werden

Angenommen, der Client sendet die folgende Anforderung:

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

Dann können wir dies so behandeln, dass ein Wert des Feldsaddress aufnull gesetzt wird oder eine solche Anforderung einfach ignoriert wird, indem sie als unverändert behandelt wird.

Wir sollten eine Strategie für den Umgang mitnull auswählen und uns bei jeder Implementierung der PATCH-Methode daran halten.

6. Fazit

In diesem kurzen Tutorial haben wir uns darauf konzentriert, die Unterschiede zwischen den Methoden HTTP PATCH und PUT zu verstehen.

Wir haben einen einfachen Spring-REST-Controller implementiert, um eine Ressource über die PUT-Methode und eine Teilaktualisierung über PATCH zu aktualisieren.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.