PUT vs PATCH vs JSON-PATCH

Eine Frage, die mit zunehmender Regelmäßigkeit in den APIs gestellt wird, die Sie nicht hassen, ist eine, die seit Jahren gestellt wird, aber nicht immer eine gute Antwort hat. Die Frage ist:

Was ist der Unterschied zwischen PUT und PATCH und wann verwende ich sie? Und WTF ist JSON-PATCH?

Zu Beginn sind PUT und PATCH zwei verschiedene HTTP-Methoden, die beide häufig in REST-APIs verwendet werden. Für Leute, die REST-APIs nur als CRUD (Create, Read, Update, Delete) betrachten, kann es zu Verwirrung kommen, wenn sie versuchen, herauszufinden, welche "die beste" ist. Die Leute haben Vorlieben, die Leute streiten sich, und tatsächlich wird das Gespräch selten auf vernünftige Weise geführt.

Sie können sowohl PUT als auch PATCH vollständig in Ihrer API haben, und nein, sie sollten kein Alias ​​voneinander sein (siehe Rails). Ganz einfach, sie machen verschiedene Dinge. Der RFC für PATCH (RFC 5789) erklärt den Unterschied tatsächlich ziemlich elegant in seiner Zusammenfassung:

Die vorhandene HTTP-PUT-Methode ermöglicht nur das vollständige Ersetzen eines Dokuments. Mit diesem Vorschlag wird die neue HTTP-Methode PATCH hinzugefügt, um eine vorhandene HTTP-Ressource zu ändern.

Eine ist dafür gedacht, wenn Sie alle Antworten kennen, und die andere dient dazu, kleine Teile gleichzeitig zu aktualisieren. Einige halten dies für einen Leistungsvorteil (weniger Material zu senden ist schneller als viel Material zu senden), aber es gibt einige rassigere Vorteile.

Konflikte

Stellen Sie sich eine Ressource mit zwei Feldern vor, field1 und field2. Zwei verschiedene Anforderungen (Anforderung A und Anforderung B) versuchen, einen dieser Feldwerte als PUT zu aktualisieren, nachdem der Anfangswert der Ressource mit einer GET-Anforderung abgerufen wurde. Sowohl Feld1 als auch Feld2 sind als Antwort auf die GET-Anforderung falsch.

Anfrage A

Aktualisieren von Feld1, um wahr zu sein.

PUT / foos / 123
{
  "field1": true,
  "field2": false
}

Anfrage B

Aktualisiere Feld2, um wahr zu sein.

PUT / foos / 123
{
  "field1": false,
  "field2": true
}

Wenn beide Felder mit "false" beginnen und jede Anforderung nur die Aktualisierung eines Felds beabsichtigt, wissen sie kaum, dass sie die Ergebnisse blockieren und sie jedes Mal im Wesentlichen zurücksetzen. Anstatt beide Werte als wahr zu bezeichnen, haben Sie einfach die letzte Anforderung, die "field1": false und "field2": true lautet.

Für einige ist dies eine Funktion, für andere ein Fehler. Wenn sie nur ein Feld aktualisieren möchten, warum müssen sie dann alles senden?

Diese Leute beschließen, nur die relevanten Felder zu senden, die sie ändern möchten. Dies ist ein offensichtlicher Missbrauch der Funktionsweise von PUT und führt zu vielen Problemen.

Erwartungen

Beim Erstellen einer API sind Sie und Ihre Mitarbeiter nicht die einzigen, die faire Erwartungen an die Funktionsweise haben müssen. Andere Systeme, wie zum Beispiel EmberJS, werden einige Erwartungen an die Funktionsweise einer PUT-Anforderung haben. Wenn Sie anfangen, PUT dazu zu bringen, teilweise Aktualisierungen zu senden, werden Sie eine schlechte Zeit haben.

Wir hatten einen Fall in der Arbeit, in dem eine EmberJS-Anwendung nur einige Modelle enthielt, die vorhandene Ressourcen in der API darstellten, die lokal aus Teildaten aufgefüllt wurden. Sie wollten den Wert eines Feldes in diesem Modell ändern und die Ressource wieder in der API speichern.

Beim Speichern bemerkte EmberData, dass es sich um einen PUT handelte, und versuchte, so viele Daten wie möglich zu senden. Da nur einige der Feldwerte vorhanden waren, wurde ein Body mit jedem unbekannten Feld als Null gesendet, wodurch wiederum Werte aus der Datenbank geleert und / oder Überprüfungsfehler für Felder ausgelöst wurden, die nicht geleert werden wollten .

PATCH das Problem

Etwas, das bei der Arbeit sehr geholfen hat, war die Implementierung von PATCH. Wir können jetzt einfach die Felder senden, die wir aktualisieren möchten, und alles andere bleibt in Ruhe.

Nehmen wir einfach an, wir verwenden JSON-API und erstellen etwas für ein Fahrgemeinschaftsunternehmen wie Ride. Wir möchten in der Lage sein, eine Reise zu starten, indem wir den Status von "pending" in "in_progress" ändern.

In v1.0 haben wir PUT verwendet. Es hat ungefähr so ​​funktioniert:

PUT / Trips / 123
{
  "Daten": [{
    "type": "trips",
    "id": "123",
    "Attribute": {
      "Status in Bearbeitung",
      "started_at": null,
      "finished_at": null
    }
    "Beziehungen": {
      "Fahrer": {
        "data": {"type": "users", "id": "999"}
      }
    }
  }
}

Hier haben wir den Status von "was auch immer" in "in_progress" geändert.

Nebenbemerkung: Soll ich das started_at selbst aktualisieren oder die API dies tun lassen? Wer weiß!

Das Hauptproblem ist hier wieder das Konfliktbeispiel oben. Wenn jemand anderes einen anderen Wert wie den des Fahrers ändert und versehentlich den Status wieder in den Status "Ausstehend" ändert, wird die Fehlermeldung "Die Reise hat bereits begonnen, kann nicht in den Status" zurückgesetzt werden "angezeigt Ich dachte: "Ich wusste gar nicht, dass es angefangen hat. Ich wollte nur, dass Gary heute fährt." und jeder wird nur verwirrt.

Die gleiche Anfrage wie ein PATCH könnte ungefähr so ​​aussehen:

PATCH / Ausflüge / 123
{
  "Daten": [{
    "type": "trips",
    "id": "123",
    "Attribute": {
      "Status in Bearbeitung"
    }
  }
}

Dies hat das Problem gelöst, andere Werte nicht versehentlich zu zerstören, da wir die Dinge nicht länger zum Wohle der Sache mitschicken. Nur die Attribute, die wir senden, sollten validiert werden. Fehlende Attribute sollten vollständig ignoriert werden.

Naja eigentlich

In RFC 5789 (RFC für die PATCH-Methode) zeigt das Beispiel, wie Folgendes funktioniert:

PATCH /file.txt HTTP / 1.1
Host: www.example.com
Content-Type: Anwendung / Beispiel
If-Match: "e0023aa4e"
Inhaltslänge: 100
[Beschreibung der Änderungen]

Diese [Beschreibung von Änderungen] wird von einigen als eine Abfolge von Operationen angesehen, die an der betreffenden Ressource ausgeführt werden müssen. Sie manifestieren sich in einer Liste von JSON-Objekten wie folgt:

PATCH / my / data HTTP / 1.1
Host: example.org
Inhaltslänge: 326
Inhaltstyp: application / json-patch + json
If-Match: "abc123"
[
  {"op": "test", "path": "/ a / b / c", "value": "foo"},
  {"op": "remove", "path": "/ a / b / c"},
  {"op": "add", "path": "/ a / b / c", "value": ["foo", "bar"]},
  {"op": "replace", "path": "/ a / b / c", "value": 42},
  {"op": "move", "from": "/ a / b / c", "path": "/ a / b / d"},
  {"op": "copy", "from": "/ a / b / d", "path": "/ a / b / e"}
]

Dieses Beispiel stammt aus RFC 6902, das auf der PATCH-Methode selbst aufbaut, um einen standardisierten Ansatz für die Angabe mehrerer atomarer Änderungen bereitzustellen. Dieser Ansatz wird als JSON PATCH bezeichnet und verfügt über einen eigenen Inhaltstyp, um zu verdeutlichen, wann er verwendet wird.

Informationen darüber zu finden ist ziemlich fruchtlos, aber es gibt ein paar Artikel, leider wird das #WellActually ziemlich schwer.

Einer der produktivsten Artikel zur Verwendung von PATCH stammt von William Durand. Es ist ein großartiger technischer Artikel, aber die Behauptungen, "mache diese wirklich komplizierte Sache, die du möglicherweise nicht brauchst oder machst es falsch", reiben mich ein bisschen in die falsche Richtung. Lesen Sie diesen Artikel und überlegen Sie, ob Sie ihn implementieren möchten. Wenn Sie ihn nicht möchten, tun Sie es nicht. Du bist in Ordnung.

Ja, JSON PATCH ist sehr schön und wird möglicherweise für Ihre API benötigt. Abhängig von der Komplexität Ihrer Aktionen, die die JSON-API zuvor für die Verwendung von JSON PATCH empfohlen hat, ist dies jedoch möglicherweise eine Komplikation, über die Sie sich keine Gedanken machen müssen entschied sich stattdessen für den Ansatz „Senden Sie einfach, was Sie brauchen“. Etwas, das dem Ansatz "Senden Sie einfach, was Sie brauchen" sehr ähnlich ist, wurde als RFC 7396 standardisiert.

Eines der besten Dinge an HTTP-basierten APIs ist die Fähigkeit, auf Content-Type-Header zu reagieren. Sie können jetzt mit einfachem JSON in Ihren PATCH-Anforderungen arbeiten und in Zukunft JSON PATCH-Unterstützung hinzufügen, wenn Sie dies benötigen. Sie können Accept-Patch (mehr hier) verwenden, um die Verfügbarkeit dieser zwei verschiedenen Modi anzuzeigen.

[anfordern]
OPTIONEN / turtles / 123 HTTP / 1.1
Host: www.amazingpets.com
[Antwort]
HTTP / 1.1 200 OK
Zulassen: GET, PUT, POST, OPTIONS, HEAD, DELETE, PATCH
Accept-Patch: application / json, application / json-patch + json

Sie können PUT auch dort behalten, wenn Sie die Idee haben möchten, idempotente Speicher zu haben. Ich habe PUT für JSON seit Jahren nicht mehr verwendet, da ich beim Erstellen von CRUDish-APIs fast nie einen idempotenten Endpunkt für dieses Zeug haben möchte, aber sie eignen sich hervorragend für Datei-Uploads, die fehlschlagen und idempotente Neuversuche erfordern könnten.