Sollte ich PATCH oder PUT in meiner REST-API verwenden?

274

Ich möchte meinen Ruheendpunkt mit der entsprechenden Methode für das folgende Szenario entwerfen.

Es gibt eine Gruppe. Jede Gruppe hat einen Status. Die Gruppe kann vom Administrator aktiviert oder inaktiviert werden.

Soll ich meinen Endpunkt als gestalten?

PUT /groups/api/v1/groups/{group id}/status/activate

ODER

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}
java_geek
quelle
1
Beide sind gut. Schauen Sie sich jedoch den RFC für das JSON PATCH-Format an ( tools.ietf.org/html/rfc6902 ). PATCH erwartet eine Art Diff / Patch-Dokument für die Nutzdaten (und Raw-JSON gehört nicht dazu).
Jørn Wildt
1
@ JørnWildt nein, PUT wäre eine schreckliche Wahl. Was legst du da hin? PATCH ist die einzig sinnvolle Option. In diesem Fall können Sie das in der Frage dargestellte PATCH-Format verwenden und einfach die PUT-Methode verwenden. Das PUT-Beispiel ist einfach falsch.
Thecoshman
3
Es ist nichts Falsches daran, eine oder mehrere Eigenschaften als eigenständige Ressourcen verfügbar zu machen, die ein Client mit PUT abrufen und ändern kann. Aber ja, die URL sollte dann / groups / api / v1 / groups / {Gruppen-ID} / status sein, auf den Sie "aktiv" oder "inaktiv" oder GET setzen können, um den aktuellen Status zu lesen.
Jørn Wildt
3
Hier ist eine gute Erklärung, wie PATCH wirklich verwendet werden sollte: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat
4
" activate" ist nicht ausreichend RESTful Konstruktion. Sie versuchen wahrscheinlich, das statusauf "aktiv" oder "deaktivierend" zu aktualisieren . In diesem Fall können Sie .../statusmit der "aktiven" oder "deaktivierenden" Zeichenfolge im Körper PATCHEN. Oder wenn Sie versuchen, einen Booleschen status.activeWert .../status/activebei zu aktualisieren , können Sie mit dem Booleschen Wert im Körper PATCHEN
Augie Gardner

Antworten:

327

Die PATCHMethode ist hier die richtige Wahl, wenn Sie eine vorhandene Ressource aktualisieren - die Gruppen-ID. PUTsollte nur verwendet werden, wenn Sie eine Ressource vollständig ersetzen .

Weitere Informationen zur teilweisen Änderung von Ressourcen finden Sie in RFC 5789 . Insbesondere wird das PUTVerfahren wie folgt beschrieben:

Einige Anwendungen, die das HTTP (Hypertext Transfer Protocol) erweitern, erfordern eine Funktion, um teilweise Ressourcenänderungen vorzunehmen. Die vorhandene HTTP-PUT-Methode ermöglicht nur das vollständige Ersetzen eines Dokuments. Dieser Vorschlag fügt eine neue HTTP-Methode, PATCH, hinzu, um eine vorhandene HTTP-Ressource zu ändern.

Luke Peterson
quelle
1
Um fair zu sein, können Sie die Zeichenfolge "Aktivieren" oder "Deaktivieren" in die Ressource einfügen. Da es nur das eine zu geben scheint, was man umschalten muss, ist es keine so große Sache, es vollständig zu ersetzen. Und es erlaubt eine (unbedeutend) kleinere Anfrage.
Thecoshman
35
Es ist wichtig zu beachten, dass sich RFC 5789 noch in der Vorschlagsphase befindet und nicht offiziell akzeptiert wurde und derzeit als "irrata exist" gekennzeichnet ist. Diese "Best Practice" ist umstritten und technisch gesehen ist PATCH noch nicht Teil des HTTP-Standards.
fishpen0
4
Nur meine 2 Cent ein paar Jahre später: Sie könnten den Status selbst als Ressource betrachten, und wenn ja, würde die Verwendung von PUT gegen / status die Statusressource an diesem Endpunkt technisch ersetzen.
Jono Stewart
3
Ich würde es wagen, gegen die Dokumente zu argumentieren, obwohl es "der" RFC ist. In den Dokumenten wird angegeben, dass Sie PATCH verwenden sollten, um nur einen Teil einer Ressource zu ändern. Dabei wurde jedoch die wichtige Sache weggelassen, dass die PATCH-Methode als nicht idempotente Methode definiert ist. Warum? Wenn die PUT-Methode unter Berücksichtigung des Aktualisierens / Ersetzens der gesamten Ressource erstellt wurde, warum wurde die PATCH-Methode dann nicht als idempotente Methode wie PUT erstellt, wenn der Zweck darin bestand, nur den Teil einer Ressource zu aktualisieren? Für mich sieht es eher nach einem Unterschied in der Idempotenz des Updates aus, wie "a = 5" (PUT) und "a = a + 5" (PATCH). Beide können die gesamte Ressource aktualisieren.
Mladen B.
179

Das R in REST steht für Ressource

(Was nicht stimmt, weil es für Repräsentativ steht, aber es ist ein guter Trick, sich an die Bedeutung von Ressourcen in REST zu erinnern).

Über PUT /groups/api/v1/groups/{group id}/status/activate: Sie sind nicht ein „activate“ zu aktualisieren. Ein "Aktivieren" ist keine Sache, es ist ein Verb. Verben sind niemals gute Ressourcen. Eine Faustregel: Wenn sich die Aktion, ein Verb, in der URL befindet, ist sie wahrscheinlich nicht RESTful .

Was machst du stattdessen? Entweder Sie "hinzufügen", "entfernen" oder "aktualisieren" eine Aktivierung für eine Gruppe, oder wenn Sie es vorziehen: Manipulieren einer "Status" -Ressource für eine Gruppe. Persönlich würde ich "Aktivierungen" verwenden, weil sie weniger mehrdeutig sind als das Konzept "Status": Das Erstellen eines Status ist mehrdeutig, das Erstellen einer Aktivierung nicht.

  • POST /groups/{group id}/activation Erstellt eine Aktivierung (oder fordert deren Erstellung an).
  • PATCH /groups/{group id}/activationAktualisiert einige Details einer vorhandenen Aktivierung. Da eine Gruppe nur eine Aktivierung hat, wissen wir, auf welche Aktivierungsressource wir uns beziehen.
  • PUT /groups/{group id}/activationFügt die alte Aktivierung ein oder ersetzt sie. Da eine Gruppe nur eine Aktivierung hat, wissen wir, auf welche Aktivierungsressource wir uns beziehen.
  • DELETE /groups/{group id}/activation Bricht die Aktivierung ab oder entfernt sie.

Dieses Muster ist nützlich, wenn die "Aktivierung" einer Gruppe Nebenwirkungen hat, z. B. Zahlungen, gesendete E-Mails usw. Nur POST und PATCH können solche Nebenwirkungen haben. Wenn beispielsweise das Löschen einer Aktivierung beispielsweise Benutzer per E-Mail benachrichtigen muss, ist LÖSCHEN nicht die richtige Wahl. In diesem Fall möchten Sie wahrscheinlich eine Deaktivierungsressource erstellen : POST /groups/{group_id}/deactivation.

Es ist eine gute Idee, diese Richtlinien zu befolgen, da dieser Standardvertrag Ihren Kunden und allen Proxies und Schichten zwischen dem Kunden und Ihnen klar macht, wann ein erneuter Versuch sicher ist und wann nicht. Angenommen, der Client befindet sich irgendwo mit flockigem WLAN und sein Benutzer klickt auf "Deaktivieren", was Folgendes auslöst DELETE: Wenn dies fehlschlägt, kann der Client es einfach erneut versuchen, bis er eine 404, 200 oder etwas anderes erhält, das er verarbeiten kann. Wenn es jedoch a auslöst POST to deactivation, weiß es, dass es nicht erneut versucht werden muss: Der POST impliziert dies.
Jeder Client hat jetzt einen Vertrag, der, wenn er befolgt wird, vor dem Versenden von 42 E-Mails schützt, "Ihre Gruppe wurde deaktiviert", einfach weil seine HTTP-Bibliothek den Anruf an das Backend wiederholt.

Aktualisieren eines einzelnen Attributs: Verwenden Sie PATCH

PATCH /groups/{group id}

Falls Sie ein Attribut aktualisieren möchten. Beispielsweise könnte der "Status" ein Attribut für Gruppen sein, das festgelegt werden kann. Ein Attribut wie "Status" ist oft ein guter Kandidat, um sich auf eine Whitelist von Werten zu beschränken. Beispiele verwenden ein undefiniertes JSON-Schema:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

Ersetzen Sie die Ressource ohne Nebenwirkungen und verwenden Sie PUT.

PUT /groups/{group id}

Falls Sie eine ganze Gruppe ersetzen möchten. Dies bedeutet nicht unbedingt, dass der Server tatsächlich eine neue Gruppe erstellt und die alte Gruppe herauswirft, z. B. können die IDs gleich bleiben. Für die Clients kann PUT jedoch Folgendes bedeuten: Der Client sollte davon ausgehen, dass er basierend auf der Antwort des Servers ein völlig neues Element erhält.

Der Client sollte im Falle einer PUTAnforderung immer die gesamte Ressource senden und über alle Daten verfügen, die zum Erstellen eines neuen Elements erforderlich sind. In der Regel sind dieselben Daten erforderlich, die für eine POST-Erstellung erforderlich sind.

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

Eine sehr wichtige Anforderung ist, dass sie PUTidempotent ist: Wenn Sie beim Aktualisieren einer Gruppe (oder beim Ändern einer Aktivierung) Nebenwirkungen benötigen, sollten Sie diese verwenden PATCH. Wenn das Update beispielsweise zum Versenden einer E-Mail führt, verwenden Sie es nicht PUT.

berkes
quelle
3
Das war sehr informativ für mich. "Dieses Muster ist nützlich, wenn die" Aktivierung "einer Gruppe Nebenwirkungen hat" - Wie kommt es, dass dieses Muster nützlich ist, insbesondere im Hinblick darauf, wann Aktionen Nebenwirkungen haben, im Gegensatz zu den anfänglichen OP-Endpunkten
Abdul
1
@Abdul, das Muster ist aus vielen Gründen nützlich, aber bei Nebenwirkungen sollte es für einen Kunden sehr klar sein, welche Auswirkungen eine Aktion hat. Wenn beispielsweise eine iOS-App beschließt, das gesamte Adressbuch als "Kontakte" zu senden, sollte klar sein, welche Nebenwirkungen das Erstellen, Aktualisieren, Löschen usw. eines Kontakts hat. So vermeiden Sie beispielsweise das Massenversenden aller Kontakte.
Berkeley
1
In RESTfull kann PUT auch die Identität der Entitäten ändern - zum Beispiel die PrimaryKey-ID, bei der eine parallele Anforderung fehlschlagen kann. (Zum Beispiel muss beim Aktualisieren der gesamten Entität einige Zeilen gelöscht und neue hinzugefügt werden, wodurch neue Entitäten erstellt werden.) Wenn PATCH dies niemals tun darf, kann eine unbegrenzte Anzahl von PATCH-Anforderungen berücksichtigt werden, ohne andere "Anwendungen" zu beeinflussen
Piotr Kula,
1
Sehr hilfreiche Antwort. Vielen Dank! Ich würde auch einen Kommentar hinzufügen, genau wie in Lukes Antwort, und darauf hinweisen, dass der Unterschied zwischen PUT / PATCH nicht nur das vollständige / teilweise Update ist, sondern auch die Idempotenz, die anders ist. Dies war kein Fehler, es war eine absichtliche Entscheidung, und ich denke, nicht viele Leute berücksichtigen dies, wenn sie über die Verwendung der HTTP-Methode entscheiden.
Mladen B.
1
@richremer-Dienste sind wie Modelle interne Abstraktionen. Ebenso wie es eine schlechte Abstraktion ist, eine 1-1-Beziehung zwischen REST-Endpunkten und ORM-Modellen oder sogar Datenbanktabellen zu erfordern, ist es eine schlechte Abstraktion, Services verfügbar zu machen. Die Außenseite, Ihre API, muss Domänenmodelle kommunizieren. Wie Sie sie intern implementieren, spielt für die API keine Rolle. Es sollte Ihnen freigestellt sein, von einem ActivationService zu einem CQRS-basierten Aktivierungsablauf zu wechseln, ohne Ihre API ändern zu müssen.
berkes
12

Ich würde die Verwendung von PATCH empfehlen, da Ihre Ressourcengruppe viele Eigenschaften hat. In diesem Fall aktualisieren Sie jedoch nur das Aktivierungsfeld (teilweise Änderung).

gemäß RFC5789 ( https://tools.ietf.org/html/rfc5789 )

Die vorhandene HTTP-PUT-Methode ermöglicht nur das vollständige Ersetzen eines Dokuments. Dieser Vorschlag fügt eine neue HTTP-Methode, PATCH, hinzu, um eine vorhandene HTTP-Ressource zu ändern.

Auch in weiteren Details,

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 modifizierte 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. dh neue Ressourcen
kann durch die Anwendung eines
PATCH erstellt oder vorhandene geändert werden .

PATCH ist weder sicher noch idempotent im Sinne von [RFC2616], Abschnitt 9.1.

Clients müssen auswählen, wann PATCH anstelle von PUT verwendet werden soll. Wenn
beispielsweise das Patchdokument größer ist als die
neuen Ressourcendaten, die in einem PUT verwendet werden, ist es möglicherweise
sinnvoll, PUT anstelle von PATCH zu verwenden. Ein Vergleich mit POST ist noch schwieriger, da POST auf sehr unterschiedliche Weise verwendet wird
und PUT- und PATCH-ähnliche Vorgänge umfassen kann, wenn der Server dies wünscht. Wenn
die Operation die durch den Anforderungs-URI identifizierte Ressource nicht auf vorhersehbare Weise ändert, sollte POST anstelle von PATCH
oder PUT berücksichtigt werden .

Der Antwortcode für PATCH lautet

Der 204-Antwortcode wird verwendet, weil die Antwort keinen Nachrichtentext enthält (den eine Antwort mit dem 200-Code haben würde). Beachten Sie, dass auch andere Erfolgscodes verwendet werden können.

Siehe auch thttp: //restcookbook.com/HTTP%20Methods/patch/

Vorsichtsmaßnahme: Eine API, die PATCH implementiert, muss atomar patchen. Es darf nicht möglich sein, dass Ressourcen auf Anforderung eines GET zur Hälfte gepatcht werden.

Clojurevangelist
quelle
7

Da Sie eine API im REST-Architekturstil entwerfen möchten, müssen Sie über Ihre Anwendungsfälle nachdenken, um zu entscheiden, welche Konzepte wichtig genug sind, um als Ressourcen verfügbar gemacht zu werden. Wenn Sie den Status einer Gruppe als Unterressource verfügbar machen möchten, können Sie ihr den folgenden URI geben und die Unterstützung für GET- und PUT-Methoden implementieren:

/groups/api/groups/{group id}/status

Der Nachteil dieses Ansatzes gegenüber PATCH zur Änderung besteht darin, dass Sie nicht in der Lage sind, Änderungen an mehr als einer Eigenschaft einer Gruppe atomar und transaktional vorzunehmen. Wenn Transaktionsänderungen wichtig sind, verwenden Sie PATCH.

Wenn Sie den Status als Unterressource einer Gruppe verfügbar machen möchten, sollte dies ein Link in der Darstellung der Gruppe sein. Wenn der Agent beispielsweise die Gruppe 123 erhält und XML akzeptiert, kann der Antworttext Folgendes enthalten:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

Ein Hyperlink wird benötigt, um die Hypermedien als Engine für die Anwendungsstatusbedingung des REST-Architekturstils zu erfüllen .

Andrew Dobrowolski
quelle
0

Ich würde im Allgemeinen etwas Einfacheres bevorzugen, wie activate/ deactivatesub-resource (verbunden durch einen LinkHeader mit rel=service).

POST /groups/api/v1/groups/{group id}/activate

oder

POST /groups/api/v1/groups/{group id}/deactivate

Für den Verbraucher ist diese Schnittstelle kinderleicht und folgt den REST-Prinzipien, ohne Sie bei der Konzeption von "Aktivierungen" als einzelne Ressourcen zu stören.

reiches remer
quelle
0

Eine mögliche Option zur Implementierung eines solchen Verhaltens ist

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

Und wenn jemand es deaktivieren muss, PUThat es natürlich den DeactivatedStatus in JSON.

Im Falle der Notwendigkeit einer Massenaktivierung / -deaktivierung PATCHkann das Spiel betreten werden (nicht für die genaue Gruppe, sondern für die groupsRessource:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

Im Allgemeinen ist dies eine Idee, wie @Andrew Dobrowolski vorschlägt, jedoch mit geringfügigen Änderungen in der genauen Realisierung.

Ivan Sokalskiy
quelle