Verwendung von PUT vs PATCH-Methoden in realen Szenarien der REST-API


680

Zunächst einige Definitionen:

PUT ist in Abschnitt 9.6 RFC 2616 definiert :

Die PUT-Methode fordert an, dass die eingeschlossene Entität unter dem angegebenen Anforderungs-URI gespeichert wird. Wenn sich der Anforderungs-URI auf eine bereits vorhandene Ressource bezieht, MUSS die eingeschlossene Entität als modifizierte Version der auf dem Ursprungsserver befindlichen Entität betrachtet werden . Wenn der Anforderungs-URI nicht auf eine vorhandene Ressource verweist und dieser URI vom anfordernden Benutzeragenten als neue Ressource definiert werden kann, kann der Ursprungsserver die Ressource mit diesem URI erstellen.

PATCH ist in RFC 5789 definiert :

Die PATCH-Methode fordert an, dass eine Reihe von Änderungen, die in der Anforderungsentität beschrieben sind, auf die durch den Anforderungs-URI identifizierte Ressource angewendet werden.

Auch gemäß RFC 2616 ist Abschnitt 9.1.2 PUT nicht korrekt, PATCH nicht.

Schauen wir uns nun ein reales Beispiel an. Wenn ich /usersmit den Daten POST mache {username: 'skwee357', email: 'skwee357@domain.com'}und der Server in der Lage ist, eine Ressource zu erstellen, antwortet er mit 201 und dem Ressourcenstandort (nehmen wir an /users/1) und jeder nächste Aufruf von GET /users/1wird zurückgegeben {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Angenommen, ich möchte meine E-Mail-Adresse ändern. E-Mail-Änderungen werden als "eine Reihe von Änderungen" betrachtet und daher sollte ich /users/1mit " Patch-Dokument " PATCHEN . In meinem Fall wäre es das JSON-Dokument : {email: 'skwee357@newdomain.com'}. Der Server gibt dann 200 zurück (vorausgesetzt, die Berechtigung ist in Ordnung). Dies bringt mich zur ersten Frage:

  • PATCH ist NICHT idempotent. Dies wurde in RFC 2616 und RFC 5789 angegeben. Wenn ich jedoch dieselbe PATCH-Anforderung (mit meiner neuen E-Mail) ausstelle, erhalte ich denselben Ressourcenstatus (wobei meine E-Mail auf den angeforderten Wert geändert wird). Warum ist PATCH dann nicht idempotent?

PATCH ist ein relativ neues Verb (RFC wurde im März 2010 eingeführt) und löst das Problem des "Pattens" oder Modifizierens einer Reihe von Feldern. Vor der Einführung von PATCH verwendeten alle PUT, um Ressourcen zu aktualisieren. Aber nachdem PATCH eingeführt wurde, bin ich verwirrt darüber, wofür PUT verwendet wird. Und das bringt mich zu meiner zweiten (und der Haupt-) Frage:

  • Was ist der wahre Unterschied zwischen PUT und PATCH? Ich habe irgendwo gelesen, dass PUT verwendet werden könnte, um die gesamte Entität unter einer bestimmten Ressource zu ersetzen , daher sollte man die vollständige Entität senden (anstelle von Attributen wie bei PATCH). Was ist die wirkliche praktische Verwendung für einen solchen Fall? Wann möchten Sie eine Entität an einem bestimmten Ressourcen-URI ersetzen / überschreiben und warum wird eine solche Operation nicht als Aktualisierung / Patching der Entität angesehen? Der einzige praktische Anwendungsfall, den ich für PUT sehe, ist die Ausgabe eines PUT für eine Sammlung, dh /usersdas Ersetzen der gesamten Sammlung. Die Ausgabe von PUT für eine bestimmte Entität ist nach Einführung von PATCH nicht sinnvoll. Liege ich falsch?

1
a) Es ist RFC 2616, nicht 2612. b) RFC 2616 ist veraltet. Die aktuelle PUT-Spezifikation befindet sich in greenbytes.de/tech/webdav/rfc7231.html#PUT . c) Ich verstehe Ihre Frage nicht. Ist es nicht ziemlich offensichtlich, dass PUT verwendet werden kann, um jede Ressource zu ersetzen, nicht nur eine Sammlung? d) Bevor PATCH eingeführt wurde, verwendeten die Leute normalerweise POST, e) schließlich, ja, eine bestimmte PATCH-Anfrage (abhängig vom Patch-Format). kann idempotent sein; es ist nur so, dass es nicht allgemein ist.
Julian Reschke

Wenn es hilft, habe ich einen Artikel über PATCH vs PUT geschrieben. eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
Äquivalent8

5
Einfach: POST erstellt ein Element in einer Sammlung. PUT ersetzt einen Artikel. PATCH ändert ein Element. Beim POSTing wird die URL für das neue Element berechnet und in der Antwort zurückgegeben, während für PUT und PATCH eine URL in der Anforderung erforderlich ist. Recht?
Tom Russell

Dieser Beitrag könnte nützlich sein: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Antworten:


941

HINWEIS : Als ich zum ersten Mal über REST gelesen habe, war Idempotenz ein verwirrendes Konzept, um zu versuchen, es richtig zu machen. Ich habe es in meiner ursprünglichen Antwort immer noch nicht ganz richtig verstanden, wie weitere Kommentare (und die Antwort von Jason Hoetger ) gezeigt haben. Für eine Weile habe ich mich geweigert, diese Antwort ausgiebig zu aktualisieren, um Jason nicht effektiv zu plagiieren, aber ich bearbeite sie jetzt, weil ich darum gebeten wurde (in den Kommentaren).

Nachdem Sie meine Antwort gelesen haben, schlage ich vor, dass Sie auch die ausgezeichnete Antwort von Jason Hoetger auf diese Frage lesen , und ich werde versuchen, meine Antwort zu verbessern, ohne einfach von Jason zu stehlen.

Warum ist PUT idempotent?

Wie Sie in Ihrem RFC 2616-Zitat festgestellt haben, wird PUT als idempotent angesehen. Wenn Sie eine Ressource platzieren, spielen diese beiden Annahmen eine Rolle:

  1. Sie beziehen sich auf eine Entität, nicht auf eine Sammlung.

  2. Die von Ihnen bereitgestellte Entität ist vollständig (die gesamte Entität).

Schauen wir uns eines Ihrer Beispiele an.

{ "username": "skwee357", "email": "skwee357@domain.com" }

Wenn Sie dieses Dokument wie vorgeschlagen an POST /userssenden, erhalten Sie möglicherweise eine Entität wie z

## /users/1

{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}

Wenn Sie diese Entität später ändern möchten, wählen Sie zwischen PUT und PATCH. Ein PUT könnte folgendermaßen aussehen:

PUT /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // new email address
}

Sie können dasselbe mit PATCH erreichen. Das könnte so aussehen:

PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

Sie werden sofort einen Unterschied zwischen diesen beiden bemerken. Der PUT enthielt alle Parameter dieses Benutzers, aber PATCH enthielt nur den Parameter, der geändert wurde ( email).

Wenn PUT verwendet wird , wird angenommen , dass Sie die komplette Einheit senden, und dass eine vollständige Einheit ersetzt zu dieser URI jede bestehende Einheit. Im obigen Beispiel erreichen PUT und PATCH dasselbe Ziel: Beide ändern die E-Mail-Adresse dieses Benutzers. PUT ersetzt dies jedoch, indem die gesamte Entität ersetzt wird, während PATCH nur die bereitgestellten Felder aktualisiert und die anderen in Ruhe lässt.

Da PUT-Anforderungen die gesamte Entität umfassen, sollte sie, wenn Sie dieselbe Anforderung wiederholt ausgeben, immer das gleiche Ergebnis haben (die von Ihnen gesendeten Daten sind jetzt die gesamten Daten der Entität). Daher ist PUT idempotent.

PUT falsch verwenden

Was passiert, wenn Sie die oben genannten PATCH-Daten in einer PUT-Anforderung verwenden?

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PUT /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(Ich gehe für die Zwecke dieser Frage davon aus, dass der Server keine spezifischen erforderlichen Felder hat und dies zulassen würde ... dies ist in der Realität möglicherweise nicht der Fall.)

Da wir PUT verwendet haben, aber nur geliefert emailhaben, ist dies das einzige in dieser Entität. Dies hat zu Datenverlust geführt.

Dieses Beispiel dient nur zur Veranschaulichung. Tun Sie dies niemals. Diese PUT-Anfrage ist technisch idempotent, aber das bedeutet nicht, dass es keine schreckliche, kaputte Idee ist.

Wie kann PATCH idempotent sein?

In dem obigen Beispiel PATCH war idempotent. Sie haben eine Änderung vorgenommen, aber wenn Sie dieselbe Änderung immer wieder vorgenommen haben, wird immer das gleiche Ergebnis zurückgegeben: Sie haben die E-Mail-Adresse auf den neuen Wert geändert.

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // email address was changed
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // nothing changed since last GET
}

Mein ursprüngliches Beispiel, aus Gründen der Genauigkeit korrigiert

Ich hatte ursprünglich Beispiele, von denen ich dachte, dass sie keine Idempotenz zeigen, aber sie waren irreführend / falsch. Ich werde die Beispiele beibehalten, sie jedoch verwenden, um eine andere Sache zu veranschaulichen: Mehrere PATCH-Dokumente für dieselbe Entität, die unterschiedliche Attribute ändern, machen die PATCHes nicht nicht idempotent.

Nehmen wir an, dass zu einem früheren Zeitpunkt ein Benutzer hinzugefügt wurde. Dies ist der Zustand, von dem aus Sie beginnen.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Nach einem PATCH haben Sie eine geänderte Entität:

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Wenn Sie Ihren PATCH dann wiederholt anwenden, erhalten Sie weiterhin das gleiche Ergebnis: Die E-Mail wurde auf den neuen Wert geändert. A geht rein, A kommt raus, deshalb ist das idempotent.

Eine Stunde später, nachdem Sie Kaffee gemacht und eine Pause gemacht haben, kommt jemand anderes mit seinem eigenen PATCH. Es scheint, dass die Post einige Änderungen vorgenommen hat.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Da sich dieser PATCH von der Post nicht mit E-Mails befasst, sondern nur mit der Postleitzahl, wird bei wiederholter Anwendung auch das gleiche Ergebnis erzielt: Die Postleitzahl wird auf den neuen Wert gesetzt. A geht rein, A kommt raus, deshalb ist das auch so idempotent.

Am nächsten Tag beschließen Sie, Ihren PATCH erneut zu senden.

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Ihr Patch hat den gleichen Effekt wie gestern: Er hat die E-Mail-Adresse festgelegt. A ging hinein, A kam heraus, daher ist dies auch idempotent.

Was ich in meiner ursprünglichen Antwort falsch verstanden habe

Ich möchte eine wichtige Unterscheidung treffen (etwas, das ich in meiner ursprünglichen Antwort falsch verstanden habe). Viele Server antworten auf Ihre REST-Anforderungen, indem sie den neuen Entitätsstatus mit Ihren Änderungen (falls vorhanden) zurücksenden. Wenn Sie diese Antwort zurückerhalten, unterscheidet sie sich von der Antwort , die Sie gestern erhalten haben , da die Postleitzahl nicht die ist, die Sie zuletzt erhalten haben. Ihre Anfrage betraf jedoch nicht die Postleitzahl, sondern nur die E-Mail. Ihr PATCH-Dokument ist also immer noch idempotent - die E-Mail, die Sie in PATCH gesendet haben, ist jetzt die E-Mail-Adresse der Entität.

Wann ist PATCH also nicht idempotent?

Für eine vollständige Behandlung dieser Frage verweise ich Sie erneut auf die Antwort von Jason Hoetger . Ich werde es dabei belassen, weil ich ehrlich gesagt nicht glaube, dass ich diesen Teil besser beantworten kann als er es bereits getan hat.


2
Dieser Satz ist nicht ganz richtig: "Aber er ist idempotent: Wenn A hineingeht, kommt B immer heraus". Wenn Sie beispielsweise GET /users/1vor der Aktualisierung der Postleitzahl GET /users/1durch die Post die gleiche Anfrage stellen und nach der Aktualisierung durch die Post erneut dieselbe Anfrage stellen würden, würden Sie zwei unterschiedliche Antworten erhalten (unterschiedliche Postleitzahlen). Das gleiche "A" (GET-Anfrage) wird eingegeben, aber Sie erhalten unterschiedliche Ergebnisse. Dennoch ist GET immer noch idempotent.
Jason Hoetger

@JasonHoetger GET ist sicher (es wird angenommen, dass es keine Änderung verursacht), aber nicht immer idempotent. Da ist ein Unterschied. Siehe RFC 2616 Sek. 9.1 .
Dan Lowe

1
@ DanLowe: GET ist definitiv idempotent. In Abschnitt 9.1.2 von RFC 2616 und in der aktualisierten Spezifikation von RFC 7231, Abschnitt 4.2.2 heißt es genau: "Von den in dieser Spezifikation definierten Anforderungsmethoden sind PUT-, DELETE- und sichere Anforderungsmethoden idempotent." Idempotenz bedeutet einfach nicht, dass Sie jedes Mal dieselbe Antwort erhalten, wenn Sie dieselbe Anfrage stellen. 7231 4.2.2 fährt fort: "Das Wiederholen der Anfrage hat den gleichen beabsichtigten Effekt, selbst wenn die ursprüngliche Anfrage erfolgreich war, obwohl die Antwort möglicherweise unterschiedlich ist. "
Jason Hoetger

1
@ JasonHoetger Ich werde das zugeben, aber ich sehe nicht, was es mit dieser Antwort zu tun hat, die PUT und PATCH besprach und niemals GET erwähnt ...
Dan Lowe

1
Ah, der Kommentar von @JasonHoetger hat es geklärt: Nur die resultierenden Zustände und nicht die Antworten mehrerer idempotenter Methodenanforderungen müssen identisch sein.
Tom Russell

328

Obwohl Dan Lowes ausgezeichnete Antwort die Frage des OP nach dem Unterschied zwischen PUT und PATCH sehr gründlich beantwortete, ist seine Antwort auf die Frage, warum PATCH nicht idempotent ist, nicht ganz richtig.

Um zu zeigen, warum PATCH nicht idempotent ist, hilft es, mit der Definition von Idempotenz zu beginnen (aus Wikipedia ):

Der Begriff idempotent wird umfassender verwendet, um eine Operation zu beschreiben, die bei einmaliger oder mehrmaliger Ausführung dieselben Ergebnisse liefert. [...] Eine idempotente Funktion hat die Eigenschaft f (f (x)) = f (x) für beliebiger Wert x.

In einer zugänglicheren Sprache könnte ein idempotenter PATCH wie folgt definiert werden: Nach dem PATCHEN einer Ressource mit einem Patchdokument ändern alle nachfolgenden PATCH-Aufrufe derselben Ressource mit demselben Patchdokument die Ressource nicht.

Umgekehrt ist eine nicht idempotente Operation eine Operation mit f (f (x))! = F (x), die für PATCH wie folgt angegeben werden könnte: Nach dem PATCHEN einer Ressource mit einem Patch-Dokument ruft nachfolgender PATCH dieselbe Ressource mit dem auf gleiches Patch - Dokument macht die Ressource ändern.

Um einen nicht-idempotenten PATCH zu veranschaulichen, nehmen wir an, dass eine / users-Ressource vorhanden ist, und nehmen wir an, dass der Aufruf GET /userseine Liste von Benutzern zurückgibt, derzeit:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

Angenommen, der Server erlaubt PATCHing / users anstelle von PATCHing / users / {id}, wie im Beispiel des OP. Lassen Sie uns diese PATCH-Anfrage stellen:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

Unser Patch-Dokument weist den Server an, einen neuen Benutzer newuserzur Liste der Benutzer hinzuzufügen . Nach dem ersten Aufruf GET /userswürde zurückkehren:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

Was passiert nun, wenn wir genau dieselbe PATCH-Anforderung wie oben ausgeben? (In diesem Beispiel nehmen wir an, dass die Ressource / users doppelte Benutzernamen zulässt.) Das "op" ist "add", sodass ein neuer Benutzer zur Liste hinzugefügt wird und ein nachfolgender Benutzer Folgendes GET /userszurückgibt:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

Die Ressource / users hat sich erneut geändert , obwohl wir genau denselben PATCH für genau denselben Endpunkt ausgegeben haben . Wenn unser PATCH f (x) ist, ist f (f (x)) nicht dasselbe wie f (x), und daher ist dieser bestimmte PATCH nicht idempotent .

Obwohl PATCH nicht garantiert idempotent ist, enthält die PATCH-Spezifikation nichts, was Sie daran hindert, alle PATCH-Vorgänge auf Ihrem bestimmten Server idempotent auszuführen. RFC 5789 erwartet sogar Vorteile von idempotenten PATCH-Anforderungen:

Eine PATCH-Anforderung kann so ausgegeben werden, dass sie idempotent ist. Dies hilft auch dabei, schlechte Ergebnisse durch Kollisionen zwischen zwei PATCH-Anforderungen auf derselben Ressource in einem ähnlichen Zeitrahmen zu verhindern.

In Dans Beispiel ist seine PATCH-Operation tatsächlich idempotent. In diesem Beispiel hat sich die Entität / users / 1 zwischen unseren PATCH-Anforderungen geändert, jedoch nicht aufgrund unserer PATCH-Anforderungen. Es war tatsächlich das andere Patch-Dokument der Post, das dazu führte , dass sich die Postleitzahl änderte. Der unterschiedliche PATCH der Post ist eine andere Operation. Wenn unser PATCH f (x) ist, ist der PATCH der Post g (x). Idempotenz gibt das an f(f(f(x))) = f(x), macht aber keine Garantie dafür f(g(f(x))).


11
Unter der Annahme, dass der Server auch die Ausgabe von PUT bei zulässt /users, würde dies PUT ebenfalls nicht idempotent machen. Alles hängt davon ab, wie der Server für die Verarbeitung von Anforderungen ausgelegt ist.
Uzair Sajid

13
Wir konnten also eine API nur mit PATCH-Operationen erstellen. Was wird dann zum REST-Prinzip bei der Verwendung von http VERBS, um CRUD-Aktionen für Ressourcen durchzuführen? Überkomplexisieren wir hier nicht die PATCH-Grenzen, meine Herren?
Bohr

6
Wenn PUT in einer Sammlung implementiert ist (z. B. /users), sollte jede PUT-Anforderung den Inhalt dieser Sammlung ersetzen. Ein PUT /userssollte also eine Sammlung von Benutzern erwarten und alle anderen löschen. Das ist idempotent. Es ist unwahrscheinlich, dass Sie so etwas auf einem / users-Endpunkt tun. Aber so etwas /users/1/emailskann eine Sammlung sein und es kann durchaus gültig sein, die gesamte Sammlung durch eine neue zu ersetzen.
Vectorjohn

5
Obwohl diese Antwort ein gutes Beispiel für Idempotenz ist, glaube ich, dass dies in typischen REST-Szenarien das Wasser trüben kann. In diesem Fall haben Sie eine PATCH-Anforderung mit einer zusätzlichen opAktion, die eine bestimmte serverseitige Logik auslöst. Dies würde erfordern, dass Server und Client die spezifischen Werte kennen, die für das opFeld übergeben werden müssen, um serverseitige Workflows auszulösen. In einfacheren REST-Szenarien ist diese Art von opFunktionalität eine schlechte Praxis und sollte wahrscheinlich direkt über HTTP-Verben verarbeitet werden.
ivandov

7
Ich würde niemals in Betracht ziehen, einen PATCH, nur POST und DELETE, gegen eine Sammlung auszustellen. Wird das wirklich jemals gemacht? Kann PATCH daher für alle praktischen Zwecke als idempotent angesehen werden?
Tom Russell

72

Ich war auch neugierig und fand ein paar interessante Artikel. Ich kann Ihre Frage möglicherweise nicht in vollem Umfang beantworten, aber dies liefert zumindest einige weitere Informationen.

http://restful-api-design.readthedocs.org/en/latest/methods.html

Der HTTP-RFC gibt an, dass PUT eine vollständig neue Ressourcendarstellung als Anforderungsentität verwenden muss. Dies bedeutet, dass wenn beispielsweise nur bestimmte Attribute bereitgestellt werden, diese entfernt werden sollten (dh auf null gesetzt werden).

Vor diesem Hintergrund sollte ein PUT das gesamte Objekt senden. Zum Beispiel,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}

Dies würde die E-Mail effektiv aktualisieren. Der Grund, warum PUT möglicherweise nicht zu effektiv ist, ist, dass Sie nur ein Feld wirklich ändern und den Benutzernamen angeben, was irgendwie nutzlos ist. Das nächste Beispiel zeigt den Unterschied.

/users/1
PUT {id: 1, email: 'newemail@domain.com'}

Wenn der PUT gemäß der Spezifikation entworfen wurde, würde der PUT den Benutzernamen auf null setzen und Sie würden Folgendes zurückerhalten.

{id: 1, username: null, email: 'newemail@domain.com'}

Wenn Sie einen PATCH verwenden, aktualisieren Sie nur das von Ihnen angegebene Feld und lassen den Rest wie in Ihrem Beispiel in Ruhe.

Die folgende Einstellung zum PATCH ist etwas anders als ich sie noch nie gesehen habe.

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

Der Unterschied zwischen den PUT- und PATCH-Anforderungen spiegelt sich in der Art und Weise wider, wie der Server die eingeschlossene Entität verarbeitet, um die durch den Anforderungs-URI identifizierte Ressource zu ändern. In einer PUT-Anforderung wird die eingeschlossene Entität als geänderte Version der auf dem Ursprungsserver gespeicherten Ressource betrachtet, und der Client fordert an, dass die gespeicherte Version ersetzt wird. Bei PATCH enthält die beigefügte Entität jedoch eine Reihe von Anweisungen, die beschreiben, wie eine Ressource, die sich derzeit auf dem Ursprungsserver befindet, geändert werden sollte, um eine neue Version zu erstellen. Die PATCH-Methode wirkt sich auf die durch den Request-URI identifizierte Ressource aus und kann auch Nebenwirkungen auf andere Ressourcen haben. Das heißt, durch die Anwendung eines PATCH können neue Ressourcen erstellt oder vorhandene geändert werden.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

Sie behandeln den PATCH mehr oder weniger als eine Möglichkeit, ein Feld zu aktualisieren. Anstatt also über das Teilobjekt zu senden, senden Sie über die Operation. dh E-Mail durch Wert ersetzen.

Der Artikel endet damit.

Es ist erwähnenswert, dass PATCH nicht wirklich für echte REST-APIs entwickelt wurde, da in der Dissertation von Fielding keine Möglichkeit definiert ist, Ressourcen teilweise zu ändern. Roy Fielding selbst sagte jedoch, dass PATCH etwas war, das [er] für den ursprünglichen HTTP / 1.1-Vorschlag erstellt hatte, da partielles PUT niemals RESTful ist. Sicher, Sie übertragen keine vollständige Darstellung, aber für REST müssen die Darstellungen ohnehin nicht vollständig sein.

Jetzt weiß ich nicht, ob ich dem Artikel besonders zustimme, wie viele Kommentatoren betonen. Das Senden über eine Teildarstellung kann leicht eine Beschreibung der Änderungen sein.

Für mich ist die Verwendung von PATCH gemischt. Zum größten Teil werde ich PUT als PATCH behandeln, da der einzige wirkliche Unterschied, den ich bisher bemerkt habe, darin besteht, dass PUT fehlende Werte auf Null setzen sollte. Es ist vielleicht nicht der 'richtigste' Weg, aber viel Glück beim Codieren.


7
Es kann sich lohnen, etwas hinzuzufügen: In William Durands Artikel (und RFC 6902) gibt es Beispiele, in denen "op" "add" ist. Dies ist offensichtlich nicht idempotent.
Johannes Brodwall

2
Oder Sie können es einfacher machen und stattdessen den RFC 7396 Merge Patch verwenden und vermeiden, Patch JSON zu erstellen.
Piotr Kula

Für NOSQL-Tabellen sind Unterschiede zwischen Patch und Put wichtig, da NOSQL keine Spalten hat
Stackdave

18

Der Unterschied zwischen PUT und PATCH besteht darin, dass:

  1. PUT muss idempotent sein. Um dies zu erreichen, müssen Sie die gesamte Ressource in den Anforderungshauptteil einfügen.
  2. PATCH kann nicht idempotent sein. Dies impliziert, dass es in einigen Fällen auch idempotent sein kann, beispielsweise in den von Ihnen beschriebenen Fällen.

PATCH benötigt eine "Patch-Sprache", um dem Server mitzuteilen, wie die Ressource geändert werden soll. Der Anrufer und der Server müssen einige "Operationen" wie "Hinzufügen", "Ersetzen", "Löschen" definieren. Zum Beispiel:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "abc@myemail.com"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "abc@myemail.com",
  "state": "NY",
  "address": "123 main street",
}

Anstatt explizite "Operations" -Felder zu verwenden, kann die Patch-Sprache dies implizit machen, indem Konventionen wie die folgenden definiert werden:

im PATCH-Anforderungshauptteil:

  1. Das Vorhandensein eines Feldes bedeutet "dieses Feld ersetzen" oder "hinzufügen".
  2. Wenn der Wert eines Feldes null ist, bedeutet dies, dass dieses Feld gelöscht wird.

Mit der obigen Konvention kann der PATCH im Beispiel die folgende Form annehmen:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "abc@myemail.com",
  "zip":
}

Was prägnanter und benutzerfreundlicher aussieht. Die Benutzer müssen sich jedoch der zugrunde liegenden Konvention bewusst sein.

Mit den oben erwähnten Operationen ist der PATCH immer noch idempotent. Wenn Sie jedoch Operationen wie "Inkrementieren" oder "Anhängen" definieren, können Sie leicht erkennen, dass diese nicht mehr idempotent sind.


7

TLDR - Dumbed Down Version

PUT => Alle neuen Attribute für eine vorhandene Ressource festlegen.

PATCH => Eine vorhandene Ressource teilweise aktualisieren (nicht alle Attribute erforderlich).


3

Lassen Sie mich den Abschnitt 4.2.2 von RFC 7231 , der bereits in früheren Kommentaren zitiert wurde, genauer zitieren und kommentieren:

Eine Anforderungsmethode wird als "idempotent" betrachtet, wenn die beabsichtigte Auswirkung mehrerer identischer Anforderungen mit dieser Methode auf den Server dieselbe ist wie die Auswirkung für eine einzelne solche Anforderung. Von den durch diese Spezifikation definierten Anforderungsmethoden sind PUT-, DELETE- und sichere Anforderungsmethoden idempotent.

(...)

Idempotente Methoden werden unterschieden, da die Anforderung automatisch wiederholt werden kann, wenn ein Kommunikationsfehler auftritt, bevor der Client die Antwort des Servers lesen kann. Wenn ein Client beispielsweise eine PUT-Anforderung sendet und die zugrunde liegende Verbindung geschlossen wird, bevor eine Antwort empfangen wird, kann der Client eine neue Verbindung herstellen und die idempotente Anforderung erneut versuchen. Es ist bekannt, dass das Wiederholen der Anforderung den gleichen beabsichtigten Effekt hat, selbst wenn die ursprüngliche Anforderung erfolgreich war, obwohl die Antwort möglicherweise unterschiedlich ist.

Was sollte also nach einer wiederholten Anforderung einer idempotenten Methode "dasselbe" sein? Nicht der Serverstatus oder die Serverantwort, sondern der beabsichtigte Effekt . Insbesondere sollte die Methode "aus Sicht des Kunden" idempotent sein. Ich denke, dieser Standpunkt zeigt, dass das letzte Beispiel in Dan Lowes Antwort , das ich hier nicht plagiieren möchte, tatsächlich zeigt, dass eine PATCH-Anfrage nicht idempotent sein kann (auf natürlichere Weise als das Beispiel in Jason Hoetgers Antwort ).

Lassen Sie uns das Beispiel etwas präziser machen, indem wir eine mögliche Absicht für den ersten Kunden explizit machen . Angenommen, dieser Client durchsucht die Liste der Benutzer mit dem Projekt, um ihre E-Mails und Postleitzahlen zu überprüfen . Er beginnt mit Benutzer 1 und bemerkt, dass die Zip richtig ist, aber die E-Mail falsch ist. Er beschließt, dies mit einer PATCH-Anfrage zu korrigieren, die völlig legitim ist und nur sendet

PATCH /users/1
{"email": "skwee357@newdomain.com"}

da dies die einzige Korrektur ist. Jetzt schlägt die Anforderung aufgrund eines Netzwerkproblems fehl und wird einige Stunden später automatisch erneut gesendet. In der Zwischenzeit hat ein anderer Client (fälschlicherweise) die Zip-Datei von Benutzer 1 geändert. Wenn Sie dann dieselbe PATCH-Anforderung ein zweites Mal senden, wird der beabsichtigte Effekt des Clients nicht erreicht, da wir eine falsche Zip-Datei erhalten. Daher ist die Methode im Sinne des RFC nicht idempotent.

Wenn der Client stattdessen eine PUT-Anforderung verwendet, um die E-Mail zu korrigieren und alle Eigenschaften von Benutzer 1 zusammen mit der E-Mail an den Server zu senden, wird seine beabsichtigte Wirkung auch dann erzielt, wenn die Anforderung später erneut gesendet werden muss und Benutzer 1 geändert wurde in der Zwischenzeit --- da die zweite PUT-Anfrage alle Änderungen seit der ersten Anfrage überschreibt.


2

Nach meiner bescheidenen Meinung bedeutet Idempotenz:

  • STELLEN:

Ich sende eine konkurrierende Ressourcendefinition. Der resultierende Ressourcenzustand entspricht genau den Definitionen der PUT-Parameter. Jedes Mal, wenn ich die Ressource mit denselben PUT-Parametern aktualisiere, ist der resultierende Status genau der gleiche.

  • PATCH:

Ich habe nur einen Teil der Ressourcendefinition gesendet, daher kann es vorkommen, dass andere Benutzer in der Zwischenzeit die ANDEREN Parameter dieser Ressource aktualisieren. Folglich können aufeinanderfolgende Patches mit denselben Parametern und ihren Werten zu unterschiedlichen Ressourcenzuständen führen. Zum Beispiel:

Angenommen, ein Objekt ist wie folgt definiert:

AUTO: - Farbe: schwarz, - Typ: Limousine, - Sitze: 5

Ich patche es mit:

{Farbe Rot'}

Das resultierende Objekt ist:

AUTO: - Farbe: rot, - Typ: Limousine, - Sitze: 5

Dann patchen einige andere Benutzer dieses Auto mit:

{Typ: 'Schrägheck'}

Das resultierende Objekt ist also:

AUTO: - Farbe: rot, - Typ: Schrägheck, - Sitze: 5

Wenn ich dieses Objekt nun erneut patche mit:

{Farbe Rot'}

Das resultierende Objekt ist:

AUTO: - Farbe: rot, - Typ: Schrägheck, - Sitze: 5

Was unterscheidet sich von dem, was ich vorher habe?

Aus diesem Grund ist PATCH nicht idempotent, während PUT idempotent ist.


1

Um die Diskussion über die Idempotenz abzuschließen, sollte ich beachten, dass man Idempotenz im REST-Kontext auf zwei Arten definieren kann. Lassen Sie uns zunächst einige Dinge formalisieren:

Eine Ressource ist eine Funktion, deren Codomäne die Klasse der Zeichenfolgen ist. Mit anderen Worten, eine Ressource ist eine Teilmenge von String × Any, bei der alle Schlüssel eindeutig sind. Nennen wir die Klasse der Ressourcen Res.

Eine REST-Operation für Ressourcen ist eine Funktion f(x: Res, y: Res): Res. Zwei Beispiele für REST-Operationen sind:

  • PUT(x: Res, y: Res): Res = x, und
  • PATCH(x: Res, y: Res): Res, was funktioniert wie PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Diese Definition ist speziell darüber zu streiten , entworfen PUTund POST, und beispielsweise nicht viel Sinn machen auf GETund POST, da es nicht über Ausdauer schert).

Jetzt durch Fixieren x: Res(informell gesprochen, mit Currying), PUT(x: Res)und PATCH(x: Res)sind univariate Funktionen des Typs Res → Res.

  1. Eine Funktion g: Res → Resgenannt wird global idempotent , wenn g ○ g == g, das heißt für alle y: Res, g(g(y)) = g(y).

  2. Lassen Sie x: Reseine Ressource und k = x.keys. Eine Funktion g = f(x)heißt left idempotent , wenn y: Reswir für jede haben g(g(y))|ₖ == g(y)|ₖ. Dies bedeutet im Grunde, dass das Ergebnis dasselbe sein sollte, wenn wir uns die angewendeten Schlüssel ansehen.

Ist PATCH(x)also nicht global idempotent, sondern bleibt idempotent. Und linke Idempotenz ist das, worauf es hier ankommt: Wenn wir ein paar Schlüssel der Ressource patchen, möchten wir, dass diese Schlüssel gleich sind, wenn wir sie erneut patchen, und wir kümmern uns nicht um den Rest der Ressource.

Und wenn RFC davon spricht, dass PATCH nicht idempotent ist, spricht es von globaler Idempotenz. Nun, es ist gut, dass es nicht global idempotent ist, sonst wäre es eine kaputte Operation gewesen.


Nun versucht Jason Hoetgers Antwort zu demonstrieren, dass PATCH nicht einmal idempotent bleibt, aber es bricht zu viele Dinge, um dies zu tun:

  • Zunächst wird PATCH für eine Menge verwendet, obwohl PATCH für die Arbeit mit Karten / Wörterbüchern / Schlüsselwertobjekten definiert ist.
  • Wenn jemand PATCH wirklich auf Mengen anwenden möchte, gibt es eine natürliche Übersetzung, die verwendet werden sollte : t: Set<T> → Map<T, Boolean>, definiert mit x in A iff t(A)(x) == True. Bei Verwendung dieser Definition bleibt das Patchen idempotent.
  • Im Beispiel wurde diese Übersetzung nicht verwendet, stattdessen funktioniert der PATCH wie ein POST. Warum wird zunächst eine ID für das Objekt generiert? Und wann wird es generiert? Wenn das Objekt zuerst mit den Elementen der Menge verglichen wird und kein passendes Objekt gefunden wird, dann wird die ID generiert, dann sollte das Programm wieder anders funktionieren ( {id: 1, email: "me@site.com"}muss mit übereinstimmen {email: "me@site.com"}, sonst ist das Programm immer kaputt und der PATCH kann unmöglich Patch). Wenn die ID generiert wird, bevor sie mit dem Set verglichen wird, ist das Programm erneut fehlerhaft.

Man kann Beispiele dafür machen, dass PUT nicht idempotent ist, wenn man die Hälfte der Dinge bricht, die in diesem Beispiel kaputt sind:

  • Ein Beispiel mit generierten zusätzlichen Funktionen wäre die Versionierung. Man kann die Anzahl der Änderungen an einem einzelnen Objekt aufzeichnen. In diesem Fall ist PUT nicht idempotent: PUT /user/12 {email: "me@site.com"}führt zu{email: "...", version: 1} beim ersten und {email: "...", version: 2}beim zweiten Mal.
  • Wenn Sie mit den IDs herumspielen, kann jedes Mal, wenn das Objekt aktualisiert wird, eine neue ID generiert werden, was zu einem nicht idempotenten PUT führt.

Alle obigen Beispiele sind natürliche Beispiele, denen man begegnen kann.


Mein letzter Punkt ist, dass PATCH nicht global idempotent sein sollte , da Sie sonst nicht den gewünschten Effekt erzielen. Sie möchten die E-Mail-Adresse Ihres Benutzers ändern, ohne den Rest der Informationen zu berühren, und Sie möchten die Änderungen einer anderen Partei, die auf dieselbe Ressource zugreift, nicht überschreiben.


-1

Eine zusätzliche Information, die ich nur hinzufügen möchte, ist, dass eine PATCH-Anforderung im Vergleich zu einer PUT-Anforderung weniger Bandbreite verbraucht, da nur ein Teil der Daten nicht die gesamte Entität gesendet wird. Verwenden Sie einfach eine PATCH-Anforderung zum Aktualisieren bestimmter Datensätze wie (1-3 Datensätze), während Sie eine PUT-Anforderung zum Aktualisieren einer größeren Datenmenge verwenden. Das ist es, denken Sie nicht zu viel und machen Sie sich nicht zu viele Sorgen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.