1. Die Ressource wird adressierbar
Ein Element zu erstellen reicht nicht: Später muss man es ersetzen, korrigieren oder löschen können. Nach POST erhält das neue Element eine id, und der Dialog mit der API ändert sich: Solange die Ressource nur erstellt wurde, arbeiten wir mit der Sammlung /reading-list; sobald das Element existiert, hat es eine eigene Adresse wie /reading-list/42.
An diesem Punkt treten PUT, PATCH und DELETE auf: Sie fügen kein neues Element zur Sammlung hinzu, sondern ändern oder löschen eine bereits vorhandene Ressource.
2. PUT: vollständige Ersetzung der Ressource
PUT – das ist eine Methode, die man sich am besten als vollständige Ersetzung des Ressourcenzustands vorstellt. Nicht „korrigiere ein Feld“, nicht „füge ein wenig hinzu“, sondern genau „hier ist die vollständige Version, wie die Ressource nach der Operation aussehen soll“. Fast wie das Speichern eines Dokuments in einem Editor: Sie senden nicht die „Differenz“ Buchstabe für Buchstabe, sondern die endgültige Version der Datei.
Auf der Domänenebene ReadLater sieht das so aus: Ein Eintrag der Leseliste existiert bereits, und wir möchten seine Daten vollständig ersetzen. Wenn der Benutzer zum Beispiel feststellt, dass der Autor falsch angegeben ist, der Status geändert und der Kommentar umformuliert werden muss, dann ist der Gedanke „vollständig ersetzen“ natürlich: Der Client nimmt die aktuelle Version des Objekts (so wie er sie versteht) und sendet sie vollständig.
Die Semantik der Anfrage ist sehr geradlinig: „Ersetze Element 42 durch genau diese Version“. Wir adressieren den Pfad des konkreten Elements, weil wir nicht mehr „zur Sammlung hinzufügen“, sondern „mit dem arbeiten, was bereits existiert“.
String method = "PUT"; // Methode zum vollständigen Ersetzen der Ressource
String path = "/reading-list/42"; // Adresse des spezifischen Elements, nicht der Sammlung
String body = """
title=Effective Java;
author=Joshua Bloch;
status=IN_PROGRESS
"""; // vereinfachter Anforderungstext (formales Format, nicht JSON)
Beachten Sie den häufigsten „Programmierer-Reflex-Fehler“: Wenn Sie in PUT nur ein Feld senden, z. B. nur title, sagen Sie dem Server eigentlich „mach die Ressource so, wie ich es beschrieben habe“. Sie haben sie aber nur unvollständig beschrieben. Folglich muss der Server entweder raten, alles zurücksetzen oder den Rest so lassen, wie er war – und hier beginnt die Magie, die wir gerade vermeiden wollen. Daher lautet die Grundregel: PUT setzt ein vollständiges Bild der Ressource voraus, selbst wenn Sie nur eine Kleinigkeit ändern möchten.
Eine kleine Analogie in Java ohne Netz: Vollständige Ersetzung bedeutet, dass Sie einfach ein neues Objekt an dieselbe Stelle legen, ohne zu versuchen, „ein paar Buchstaben auszuschneiden und einzufügen“.
import java.util.HashMap;
import java.util.Map;
record Item(String title, String author) {}
Map<Long, Item> items = new HashMap<>(); // vereinfachter Speicher nach id
items.put(42L, new Item("Clean Code", "Robert C. Martin")); // ursprünglicher Zustand der Ressource 42
items.put(42L, new Item("Effective Java", "Joshua Bloch")); // vollständige Ersetzung des Werts nach Schlüssel (Analogon zu PUT)
System.out.println(items.get(42L)); // Item[title=Effective Java, author=Joshua Bloch]
Hier ist der Schlüsseleffekt im Kopf: Wir haben nicht den Autor „angeklebt“ oder den Status „nachgebessert“, sondern den Wert unter dem Schlüssel 42 vollständig ersetzt. In HTTP-Begriffen ist PUT ungefähr so: Sie greifen auf die Ressource über ihre id zu und bringen eine „neue vollständige Version“ der Ressource mit.
Ein weiterer wichtiger Bezug ist der Unterschied zwischen POST und PUT in der Bedeutung. POST bedeutet meistens „erstelle ein neues Element“, und der Server entscheidet dann normalerweise selbst, welche id vergeben wird. PUT bedeutet meistens „ändere ein vorhandenes Element mit einer bestimmten id“, und diese id steht bereits im Pfad. Ja, in manchen Systemen wird PUT auch verwendet, um „nach einer bestimmten ID zu erstellen“, aber das ist eine separate Vertragsentscheidung. Innerhalb unseres einfachen Modells, um nicht zu früh in Nuancen zu versinken, halten wir uns an eine einfache Regel: POST erstellt ein neues Element, PUT ersetzt ein vorhandenes vollständig.
3. PATCH: teilweise Änderung der Ressource
PATCH kommt genau dort ins Spiel, wo PUT unbequem wird. Im wirklichen Leben möchte man oft nur einen kleinen Teil einer Ressource ändern, ohne das gesamte Objekt zu übertragen. Das ist wie eine „chirurgische Korrektur“: Status korrigieren, Kommentar hinzufügen, ein Feld ändern – und den Rest in Ruhe lassen. Das ist die grundlegende Intuition von PATCH.
Aber hier ist es wichtig, gleich Disziplin zu vereinbaren, sonst wird PATCH zur Methode „mach, was du willst“. Eine teilweise Änderung funktioniert gut, wenn sie eng und klar ist. Wenn Sie „alles Mögliche patchen“, zwingen Sie den Server, komplexe Regeln zu unterstützen: Was bedeutet das Fehlen eines Feldes im PATCH-Body (nicht ändern? zurücksetzen? löschen?), wie unterscheidet man null als Wert von „Feld fehlt“ und so weiter. Das sind alles reale Fragen, aber wir konzentrieren uns vorerst auf den eigentlichen Sinn der Methode.
In der ReadLater-Domäne ist ein sehr natürliches Beispiel für PATCH die Änderung des Lesestatus. Der Benutzer hat ein Buch gelesen und möchte PLANNED in FINISHED ändern. Er möchte nicht das gesamte Objekt mit Titel, Autor und Kommentaren übertragen. Er möchte nur eines: den Status ändern.
Aus Sicht der Anfrage klingt das so: „ändere einen Teil der Ressource 42“ – und wir adressieren die Ressource über ihren Pfad:
String method = "PATCH"; // Methode zur teilweisen Änderung der Ressource
String path = "/reading-list/42"; // Adresse des zu ändernden Elements
String body = "status=FINISHED"; // ändere nur ein Feld (Idee der „chirurgischen Korrektur“)
Auch hier diskutieren wir noch nicht das Format des Anforderungstextes (JSON werden wir später ausführlich behandeln). Wichtig ist jetzt nur, dass PATCH eine teilweise Änderung darstellt, keine vollständige Ersetzung.
Analog in Java (wieder ohne Netzwerk): Eine teilweise Änderung ist, wenn Sie ein vorhandenes Objekt nehmen und eine neue Version erstellen, indem Sie nur das gewünschte Feld ändern und die anderen so übernehmen, wie sie sind. Bei einem record ist das besonders deutlich: Da das Objekt unveränderlich ist, müssen Sie explizit eine „neue Version“ aus der alten zusammenbauen.
import java.util.HashMap;
import java.util.Map;
record Item(String title, String author, String status) {}
Map<Long, Item> items = new HashMap<>();
items.put(42L, new Item("Clean Code", "Robert C. Martin", "PLANNED")); // ursprünglicher Zustand der Ressource
Item oldItem = items.get(42L); // lesen die aktuelle Version (das, was bereits existiert)
items.put(42L, new Item(oldItem.title(), oldItem.author(), "FINISHED")); // ändern nur status, den Rest übernehmen wir
System.out.println(items.get(42L).status()); // FINISHED
Der wichtige Gedanke hier: Wir beschreiben nicht „die Ressource neu“, sondern ändern genau einen Teil und behalten den Rest explizit bei. Vom Sinn her ist das das, was PATCH tut.
Wenn man eine noch „praxisnähere“ Formulierung möchte: PUT bedeutet „das gesamte Dokument überschreiben“, PATCH bedeutet „eine Korrektur im Dokument vornehmen“. Denken Sie nur daran, dass die „Korrektur“ vertraglich klar sein muss. Wenn Sie selbst nicht kurz erklären können, was Ihr PATCH genau tut, ist der Vertrag wahrscheinlich schwammig.
4. DELETE: Löschen der Ressource
DELETE ist der „ehrlichste“ aus diesem Trio: Es sagt genau das, was es tut. Wenn die Ressource existiert, bedeutet DELETE „lösche sie“. Wenn wir weiterhin in Ressourcen denken, erfolgt das Löschen ebenfalls nicht „allgemein“, sondern in Bezug auf ein bestimmtes Objekt. Daher zielt ein typisches DELETE auf den Pfad des Elements, nicht auf den Pfad der Sammlung.
In der ReadLater-Domäne sieht das einfach aus: Der Benutzer löscht ein bestimmtes Buch aus seiner Leseliste. Also greift er auf /reading-list/42 zu und fordert das Löschen des Elements 42. Es ist fast nie sinnvoll, einen body zu übergeben: Löschen ist eine Aktion, die allein durch die Adressierung der konkreten Ressource definiert wird.
String method = "DELETE"; // Methode zum Löschen der Ressource
String path = "/reading-list/42"; // Adresse des spezifischen Elements, das gelöscht werden soll
Eine Analogie in Java ist elementar: Löschen ist remove. Und, was wichtig ist, das Ergebnis des Löschens ist das Erreichen des Endzustands „das Element existiert nicht mehr“.
import java.util.HashMap;
import java.util.Map;
Map<Long, String> items = new HashMap<>();
items.put(42L, "Clean Code"); // Element existiert
items.remove(42L); // entfernen nach Schlüssel (Analogon zu DELETE per id)
System.out.println(items.containsKey(42L)); // false
Eine häufige Verwechslung bei Anfängern hier: „Warum nicht DELETE /reading-list?“ Theoretisch möglich. Aber im Ressourcenmodell würde das das Löschen der gesamten Sammlung oder das vollständige Leeren der Sammlung bedeuten. Das kommt vor, ist aber ein anderer Sinn und für die meisten Anwendungs-APIs aus UX-Sicht gefährlich: Eine falsche Anfrage – und „tschüss, ganze Leseliste“. Daher ist die grundlegende und sichere Gewohnheit für CRUD-Szenarien: Wir löschen ein bestimmtes Element über DELETE auf seiner Adresse.
5. Spickzettel zur Methodenauswahl
Wenn Sie zum ersten Mal eine API erstellen oder einfach nur versuchen, sie zu lesen, möchte das Gehirn sich gerne auf einen einfachen „Spickzettel“ stützen. Das ist normal: Sie müssen noch nicht alles „automatisch“ können. Hauptsache, der Spickzettel orientiert sich am Sinn und nicht an der Gewohnheit. Unten finden Sie eine Tabelle, die hilft, die Methode genau nach ihrem Effekt auszuwählen.
Gleich vorweg: Wir besprechen hier nicht die numerischen Statuscodes, Header und andere Antwortdetails. Wir wählen die Methode jetzt nur nach der Bedeutung der Aktion auf die Ressource aus.
| Situation aus Clientsicht | Methode | Wohin adressieren | Bedeutung der Anfrage in menschlicher Sprache | Beispiel in der ReadLater-Domäne |
|---|---|---|---|---|
| „Zeige Liste“ | GET | Sammlung | „Gib aktuelle Daten“ | GET /reading-list |
| „Zeige ein Element“ | GET | Element | „Gib Daten einer bestimmten Ressource“ | GET /reading-list/42 |
| „Füge neues Element hinzu“ | POST | Sammlung | „Erstelle ein neues Element innerhalb der Sammlung“ | POST /reading-list |
| „Mach das Element *genau so komplett*“ | PUT | Element | „Ersetze den Zustand der Ressource vollständig“ | PUT /reading-list/42 |
| „Ändere nur einen Teil“ | PATCH | Element | „Ändere die Ressource teilweise, lass den Rest so“ | PATCH /reading-list/42 |
| „Lösche Element“ | DELETE | Element | „Entferne die Ressource“ | DELETE /reading-list/42 |
Wenn man dieses logische Schema im Kopf behält, verliert vieles seinen Zauber. Eine Anfrage hat immer ein „Objekt, auf das wir uns beziehen“ (Pfad) und eine „Aktion darauf“ (Methode). Und je weniger Sie versuchen, die Aktion in den Pfad zu stecken (/deleteItem/42), desto einfacher und vorhersagbarer wird der Vertrag.
6. Änderungen in ReadLater: Szenarien
Wenn wir „Ressource aktualisieren“ sagen, klingt das abstrakt, als würden wir nicht über eine Anwendung, sondern über Philosophie diskutieren. Kehren wir zurück auf die Erde, zu unserer Domäne, wo alles sehr menschlich ist: Der Benutzer führt eine Liste von Büchern und ändert manchmal Informationen über Einträge. Im wirklichen Leben gibt es normalerweise nur wenige Arten von Änderungen, und sie passen direkt zu PUT, PATCH und DELETE.
Die vollständige Ersetzung (PUT) tritt auf, wenn der Benutzer die gesamte Karte bearbeitet. Zum Beispiel hat er sowohl den Autor als auch den Titel und den Kommentar und sogar die externe Kennung (wenn wir das Buch mit einem Katalog verknüpfen) korrigiert. In einer solchen Situation ist der Gedanke „hier ist die vollständig aktualisierte Version des Elements“ natürlich, und PUT spiegelt diesen Sinn gut wider: „Mach Element 42 genau so“.
Die teilweise Änderung (PATCH) passt gut zu Situationen mit einem „Schalterknopf“. Das anschaulichste Beispiel ist der Lesestatus. Sie haben eine Dropdown-Liste oder einen Button „Als gelesen markieren“ und möchten genau einen Parameter ändern. Die vollständige Karte für einen einzigen Status zu senden, ist unnötiger Aufwand; PATCH drückt die Idee der teilweisen Änderung ohne überflüssige Bewegungen aus.
Das Löschen (DELETE) ist, wenn ein Element aus der Liste entfernt werden muss. Entweder ist das Buch nicht mehr relevant oder der Benutzer hat es versehentlich hinzugefügt. Es gibt hier überhaupt keinen Sinn, über Felder zu diskutieren: Die Aktion ist „Ressource löschen“, und das wird direkt in der Methode abgebildet.
Ganz kurz gesagt: Für das Lesen und Hinzufügen verwenden wir bereits GET und POST, aber für das Leben nach der Erstellung – da kommt der „Wartungsmodus“ der Ressource: vollständig ersetzen (PUT), einen Teil korrigieren (PATCH), löschen (DELETE). Und das ist genau die Semantik, die der Client am einfachsten auf einen Blick lesen kann: „METHODE + PFAD“.
7. Typische Fehler bei PUT, PATCH und DELETE
Fehler bei diesem Thema sind normalerweise nicht syntaktisch, sondern inhaltlich: Sie können eine perfekt korrekte HTTP-Anfrage senden, und der Server wird sie sogar verarbeiten, aber der Vertrag verhält sich seltsam. Das Unangenehmste ist, dass solche Fehler oft nicht sofort auftreten, sondern wenn Client und Server mit unterschiedlichen Erwartungen zu leben beginnen und Sie irgendwo nach einer Woche fragen: „Warum ist bei uns das Feld comment verschwunden?“
Fehler Nr. 1: PUT als „ändere ein Feld“ verwenden, ohne den Effekt der vollständigen Ersetzung zu verstehen.
Das ist ein Klassiker. Der Client sendet PUT /reading-list/42 und übergibt nur title, weil genau das im Formular geändert wurde. Der Server aber, der der Idee der „vollständigen Ersetzung“ folgt, ersetzt die Ressource ehrlich durch ein „Objekt, das nur title enthält“. In der Folge verschwinden Autor, Status und Kommentar oder werden zurückgesetzt. Wenn Sie ein einzelnes Feld ändern möchten, muss der Client entweder die vollständige Version der Ressource senden, oder der Vertrag muss PATCH mit einer klar definierten Bedeutung verwenden.
Fehler Nr. 2: PATCH zu einem „universellen Update für alles Mögliche“ ohne klaren Vertrag machen.
Die Versuchung ist verständlich: „PATCH ist ein teilweises Update, also kann ich beliebige Feldkombinationen senden.“ Aber ohne strenge Regeln stoßen Sie schnell auf unklare Fragen: Bedeutet das Fehlen eines Feldes „nicht ändern“ oder „löschen“? Und null – bedeutet das „löschen“ oder „Wert unbekannt“? Selbst wenn Sie noch kein JSON verwenden, bleibt das Problem inhaltlich. Ein guter PATCH ist in der Regel recht eng und vorhersehbar: „ändere den Status“ oder „ändere den Kommentar“. Je klarer die Grenzen, desto weniger Überraschungen.
Fehler Nr. 3: Versuchen, über den Sammlungspfad zu aktualisieren oder zu löschen, und dabei die Adressierbarkeit des Elements vergessen.
Anfänger schreiben manchmal so etwas wie DELETE /reading-list in der Annahme „ich lösche ja aus der Liste“. Aber das Ressourcenmodell liest das anders: Sie löschen (oder leeren) die gesamte Liste, nicht ein einzelnes Element. In den meisten Anwendungsszenarien benötigen Sie eine punktgenaue Aktion auf ein Element, also den Pfad des Elements: /reading-list/{id}.
Fehler Nr. 4: POST und PUT verwechseln, weil „beide haben einen body“.
Ja, sowohl POST als auch PUT können Daten im Anforderungstext enthalten. Aber das ist eine Denkfalle: Wichtig ist nicht die Form, sondern der Sinn. POST ist im einfachen Modell die Erstellung eines neuen Elements innerhalb der Sammlung, PUT die vollständige Ersetzung eines bereits vorhandenen Elements. Wenn Sie anfangen, „über POST zu aktualisieren“, wachsen Sie fast unweigerlich in eine chaotische API hinein, bei der unter einem Pfad viele verschiedene Befehle existieren und der Client raten muss, was passieren wird.
Fehler Nr. 5: Verben in den Pfad aufnehmen und damit die Ressourcenlesbarkeit zerstören.
Pfade wie /updateReadingListItem/42 oder /deleteBook/42 wirken vertraut für jemanden, der im Kopf Befehle schreibt, statt mit Ressourcen zu arbeiten. Aber in HTTP ist der Sinn der Operation bereits durch die Methode ausgedrückt. Wenn Sie die Aktion in den Pfad stecken, verkomplizieren Sie den Vertrag: Der Pfad hört auf, ein Objektname zu sein, und wird zum Namen einer Prozedur. Dann tauchen neue Prozeduren, neue „verbale“ Pfade auf, und die API breitet sich aus wie der Ordner Downloads.
GO TO FULL VERSION