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:
Sie beziehen sich auf eine Entität, nicht auf eine Sammlung.
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 /users
senden, 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 email
haben, 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.