REST: Aktualisieren mehrerer Ressourcen mit einer Anforderung - Ist dies Standard oder sollte vermieden werden?

74

Eine einfache REST-API:

  • GET: items / {id} - Gibt eine Beschreibung des Elements mit der angegebenen ID zurück
  • PUT: items / {id} - Aktualisiert oder erstellt das Element mit der angegebenen ID
  • LÖSCHEN: items / {id} - Löscht das Element mit der angegebenen ID

Nun die fragliche API-Erweiterung:

  • GET: items? Filter - Gibt alle Artikel-IDs zurück, die dem Filter entsprechen
  • PUT: items - Aktualisiert oder erstellt eine Reihe von Elementen, wie in der JSON-Nutzlast beschrieben
  • [[ LÖSCHEN: Elemente - löscht eine Liste der von JSON-Nutzdaten beschriebenen Elemente ]] <- Nicht korrekt

Ich interessiere mich jetzt für die Recyclingfunktion für DELETE- und PUT-Vorgänge, auf die PUT / DELETE-Elemente / {id} problemlos zugreifen können.

Frage: Ist es üblich, eine solche API bereitzustellen?

Alternative: Im Zeitalter der Einzelverbindung ist die Ausgabe mehrerer Anfragen kostengünstig und würde atomarer funktionieren, da eine Änderung entweder erfolgreich ist oder fehlschlägt. Im Zeitalter der NOSQL-Datenbank ist jedoch möglicherweise bereits eine Änderung in der Liste eingetreten, selbst wenn die Anforderungsverarbeitung mit abbricht interner Server oder was auch immer aus irgendeinem Grund.

[AKTUALISIEREN]

Nach Berücksichtigung der Webstandards des Weißen Hauses und von Wikipedia: REST-Beispiele ist nun die folgende Beispiel-API vorgesehen:

Eine einfache REST-API:

  • GET: items / {id} - Gibt eine Beschreibung des Elements mit der angegebenen ID zurück
  • PUT: items / {id} - Aktualisiert oder erstellt das Element mit der angegebenen ID
  • LÖSCHEN: items / {id} - Löscht das Element mit der angegebenen ID

Top-Ressourcen-API:

  • GET: items? Filter - Gibt alle Artikel-IDs zurück, die dem Filter entsprechen
  • POST: items - Aktualisiert oder erstellt eine Reihe von Elementen, wie in der JSON-Nutzlast beschrieben

PUT und DELETE on / items wird nicht unterstützt und ist verboten.

Die Verwendung von POST scheint der Trick zu sein, neue Elemente in einer umschließenden Ressource zu erstellen, ohne sie zu ersetzen, sondern anzuhängen.

HTTP Semantics POST Reads:

Erweitern einer Datenbank durch Anhängen

Wo die PUT-Methoden erfordern würden, die gesamte Sammlung zu ersetzen, um eine äquivalente Darstellung zurückzugeben, wie von HTTP Semantics PUT angegeben :

Ein erfolgreicher PUT einer bestimmten Darstellung würde bedeuten, dass ein nachfolgendes GET auf derselben Zielressource dazu führt, dass eine äquivalente Darstellung in einer 200 (OK) -Antwort zurückgegeben wird.

[UPDATE2]

Eine Alternative, die für den Aktualisierungsaspekt mehrerer Objekte noch konsistenter erscheint, scheint die PATCH-Methode zu sein. Der Unterschied zwischen PUT und PATCH wird im Entwurf des RFC 5789 wie folgt beschrieben :

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.

Im Vergleich zu POST ist PATCH möglicherweise auch eine bessere Idee, da PATCH ein UPDATE zulässt, bei dem POST nur das Anhängen von Bedeutungen ohne die Möglichkeit einer Änderung zulässt.

POST scheint hier also falsch zu sein und wir müssen unsere vorgeschlagene API ändern in:

Eine einfache REST-API:

  • GET: items / {id} - Gibt eine Beschreibung des Elements mit der angegebenen ID zurück
  • PUT: items / {id} - Aktualisiert oder erstellt das Element mit der angegebenen ID
  • LÖSCHEN: items / {id} - Löscht das Element mit der angegebenen ID

Top-Ressourcen-API:

  • GET: items? Filter - Gibt alle Artikel-IDs zurück, die dem Filter entsprechen
  • POST: items - Erstellt ein oder mehrere Elemente, wie in der JSON-Nutzlast beschrieben
  • PATCH: items - Erstellt oder aktualisiert ein oder mehrere Elemente, wie in der JSON-Nutzlast beschrieben
Martin Kersten
quelle
Kann helfen: github.com/WhiteHouse/api-standards#http-verbs . Übrigens haben DELETE-Anforderungen keine definierte Körpersemantik ( stackoverflow.com/a/5928241/1225328 ).
sp00m

Antworten:

50

Sie könnten die Sammlung PATCHEN, z

PATCH /items
[ { id: 1, name: 'foo' }, { id: 2, name: 'bar' } ]

Technisch gesehen würde PATCH den Datensatz in der URL identifizieren (dh PATCH /items/1und nicht im Anforderungshauptteil, aber dies scheint eine gute pragmatische Lösung zu sein.

Um das Löschen, Erstellen und Aktualisieren in einem einzigen Aufruf zu unterstützen, wird dies von Standard-REST-Konventionen nicht wirklich unterstützt. Eine Möglichkeit ist ein spezieller "Batch" -Dienst, mit dem Sie Anrufe zusammenstellen können:

POST /batch
[
  { method: 'POST', path: '/items', body: { title: 'foo' } },
  { method: 'DELETE', path: '/items/bar' }
]

Dies gibt eine Antwort mit Statuscodes für jede eingebettete Anforderung zurück:

[ 200, 403 ]

Nicht wirklich Standard, aber ich habe es geschafft und es funktioniert.

Mahemoff
quelle
Eine Batch-Idee ist interessant. Wie haben Sie es umgesetzt? Ist es ein echter Versand oder wird er nur intern abgewickelt? Würde das Ausgeben mehrerer Anforderungen anstelle einer einzelnen Anforderung zu einer solchen Leistungsbeeinträchtigung (Latenz, Bandbreite) führen, wenn man bedenkt, dass heute nur eine Verbindung benötigt wird?
Martin Kersten
Anfänglich wurde es intern und getrennt von den einzelnen Abfragen behandelt, hauptsächlich aus Leistungsgründen. (Sie können Optimierungen vornehmen, z. B. eine einzelne SQL-Abfrage anstelle von N Abfragen ausführen.) Aufgrund einiger verwirrender Inkonsistenzen wurde diese jedoch überarbeitet, sodass derselbe Code effektiv zwischen den Stapelbefehlen und den "echten" Atombefehlen geteilt wird. Es ist ein bisschen mehr ein Leistungseinbruch, obwohl Sie dies ein wenig durch Caching und Optimierung abmildern können, wenn es notwendig ist.
Mahemoff
Wenn Sie HTTP / 2 verwenden können, ist das Schema wahrscheinlich nicht erforderlich, da der Leistungsaufwand für mehrere Anrufe wahrscheinlich recht gering ist.
Mahemoff
Ich habe darüber nachgedacht, einen einzelnen Stapel (Proxy / Dispatcher) bereitzustellen, indem eine Allzweckmethode bereitgestellt wird, die eine Reihe definierter Anforderungen akzeptiert und diese nur an dieselbe Serverinstanz oder intern im Rechenzentrum erneut ausgibt und die Ergebnisse sammelt. Dies kann besonders nützlich sein, wenn Sie Dinge gruppieren oder eine bessere Komprimierung, gemeinsame Nutzung wiederholter Parameter und Referenzen sowie allerlei Möglichkeiten bereitstellen. Im Grunde genommen wird es als echte Anfrage im Namen des Originalplakats versandt.
Martin Kersten
1
Zustimmen. Das wäre eher ein Taskplaner oder ein Workflow-Prozessor. Sie müssen jedoch angeben, welcher Teil des Stapels parallel verarbeitet werden kann und welcher eine Sequenz bildet, die einfach durch Hinzufügen einer Abstraktionsebene wie "Aufgaben" erstellt werden kann: ["A": [Anforderung, Anforderung], "B. ": [...]]. Wäre auch schön für bestimmte Arten von Abnahmetests, da man nur JSON-Befehle und eine Erwartung in JSON schreiben muss. Wäre sehr gut für REST-API-Tests. Ich denke daran.
Martin Kersten
24

Aktualisieren mehrerer Ressourcen mit einer Anfrage - Ist dies Standard oder sollte vermieden werden?

Nun, manchmal müssen Sie einfach atomare Stapeloperationen oder andere ressourcenbezogene Operationen ausführen, die einfach nicht zum typischen Schema einer einfachen REST-API passen, aber wenn Sie es benötigen, können Sie es nicht vermeiden.

Ist es Standard?

Es gibt keinen allgemein anerkannten REST-API-Standard, daher ist diese Frage schwer zu beantworten. Wenn Sie sich jedoch einige häufig zitierte API-Designrichtlinien wie jsonapi.org , restfulapi.net , das Microsoft API- Designhandbuch oder die REST-API-Konventionen von IBM ansehen , in denen Batch-Vorgänge nicht erwähnt werden, können Sie daraus schließen, dass solche Vorgänge nicht allgemein verstanden werden Dies ist eine Standardfunktion von REST-APIs.

Das sei gesagt, eine Ausnahme ist der Google - API - Design - Guide , das erwähnt die Schaffung von „custom“ Methoden , die durch die Verwendung eines Doppelpunktes über eine Ressource zugeordnet werden können, zum Beispiel https://service.name/v1/some/resource/name:customVerb, sie erwähnt auch ausdrücklich Batch - Operationen als Anwendungsfall:

Eine benutzerdefinierte Methode kann einer Ressource, einer Sammlung oder einem Dienst zugeordnet werden. Es kann eine beliebige Anforderung annehmen und eine beliebige Antwort zurückgeben und unterstützt auch Streaming-Anforderungen und -Antworten. [...] Benutzerdefinierte Methoden sollten das HTTP-POST-Verb verwenden, da es die flexibelste Semantik aufweist. [...] Für leistungskritische Methoden kann es hilfreich sein, benutzerdefinierte Stapelmethoden bereitzustellen, um den Overhead pro Anforderung zu reduzieren .

In dem von Ihnen angegebenen Beispielfall gehen Sie gemäß dem API-Handbuch von Google wie folgt vor:

POST /api/items:batchUpdate

Außerdem haben einige öffentliche APIs beschlossen, einen zentralen /batchEndpunkt anzubieten , z. B. die Google Mail-API von Google .

Außerdem ist , wie erwähnt bei restfulapi.net, gibt es auch das Konzept einer Ressource „Store“, in dem Sie speichern und abrufen ganze Listen von Elementen auf einmal über PUT - aber dieses Konzept nicht für serververwalteten Ressource Sammlungen zählt:

Ein Geschäft ist ein vom Client verwaltetes Ressourcen-Repository. Mit einer Speicherressource kann ein API-Client Ressourcen einfügen, wieder herausholen und entscheiden, wann sie gelöscht werden sollen. Ein Geschäft generiert niemals neue URIs. Stattdessen verfügt jede gespeicherte Ressource über einen URI, der von einem Client ausgewählt wurde, als er ursprünglich in den Speicher gestellt wurde.


Nachdem Sie Ihre ursprünglichen Fragen beantwortet haben, finden Sie hier einen weiteren Ansatz für Ihr Problem, der noch nicht erwähnt wurde. Bitte beachten Sie, dass dieser Ansatz etwas unkonventionell ist und nicht so hübsch aussieht wie das typische Namensschema für REST-API-Endpunkte. Ich persönlich verfolge diesen Ansatz nicht, aber ich hatte immer noch das Gefühl, dass darüber nachgedacht werden sollte :)

Die Idee ist, dass Sie über Ihr Endpunktpfad-Namensschema zwischen CRUD-Operationen für eine Ressource und anderen ressourcenbezogenen Operationen (z. B. Stapeloperationen) unterscheiden können.

Stellen Sie sich beispielsweise eine RESTful-API vor, mit der Sie CRUD-Vorgänge für eine "Unternehmens" -Ressource ausführen können, und Sie möchten auch einige "Unternehmens" -bezogene Vorgänge ausführen, die nicht in das ressourcenorientierte CRUD-Schema passen, das normalerweise mit Restful-APIs verbunden ist - wie die von Ihnen erwähnten Stapeloperationen.

Anstatt Ihre Ressourcen direkt darunter anzuzeigen /api/companies(z. B. /api/companies/22), können Sie jetzt unterscheiden zwischen:

  • /api/companies/items - dh eine Sammlung von Unternehmensressourcen
  • /api/companies/ops - dh Operationen im Zusammenhang mit Unternehmensressourcen

Für itemsdie üblichen RESTful-APIs gelten http-Methoden und Benennungsschemata für Ressourcen-URLs (z. B. wie hier oder hier beschrieben ).

POST    /api/companies/items
GET     /api/companies/items
GET     /api/companies/items/{id}
DELETE  /api/companies/items/{id}
PUT     /api/companies/items/{id}

Jetzt können Sie für unternehmensbezogene Vorgänge das /api/companies/ops/Routenpräfix und Anrufvorgänge über POST verwenden.

POST    /api/companies/ops/batch-update
POST    /api/companies/ops/batch-delete
POST    /api/companies/ops/garbage-collect-old-companies
POST    /api/companies/ops/increase-some-timestamps-just-for-fun
POST    /api/companies/ops/perform-some-other-action-on-companies-collection

Da POST-Anforderungen nicht zur Erstellung einer Ressource führen müssen, ist POST hier die richtige Methode:

Die von der POST-Methode ausgeführte Aktion führt möglicherweise nicht zu einer Ressource, die durch einen URI identifiziert werden kann. https://tools.ietf.org/html/rfc2616#section-9.5

B12Toaster
quelle
2
Ich finde die Google-Doppelpunktkonvention aus ästhetischer Sicht ein wenig auf der Nase. Wenn Sie ein neues Verb erfinden, ist es üblicher, einen Schrägstrich anzuhängen und etwas daran zu senden - POST /api/items/batch-update. Ich werde voll und ganz anerkennen, dass es einen Nachteil gibt, nämlich dass Sie jetzt ein reserviertes Wort haben, das nicht für eine Kinderassoziation verwendet werden kann, falls Kinderassoziationen benötigt werden, aber ich würde diesen Kompromiss für sauberere URLs eingehen .
Mahemoff
1
@ Mahemoff guter Punkt. Was Sie vorschlagen, ist eine Art Shortcut-Version der oben erwähnten Technik. In meiner Antwort habe ich Operationen von Ressourcen getrennt, um (a) Namespace-Konflikte zwischen Verben und Ressourcen zu vermeiden und (b) der Idee zu entsprechen, dass in einer REST-URL ein "Pfadsegment" nur Ressourcen des "gleichen" Typs enthält. Wenn man bedenkt, dass generierte IDs normalerweise ohnehin UUIDs oder Ganzzahlen sind, die sich programmgesteuert leicht von einem benutzerdefinierten "Verb" ( /api/users/3vs /api/users/batch-update) unterscheiden lassen und sich Namespaces daher nicht überschneiden, ist Ihr Ansatz ebenfalls sinnvoll.
B12Toaster
There is no universally accepted REST API standard Deshalb ist es ein beschissener Standard. Keine Unterstützung für atomare Batch-Operationen bedeutet, dass dieser 'Standard' nicht für die Prime-Time bereit ist. Ich erstelle Finanzanwendungen und eine atomare Stapeloperation (mit optimistischer Parallelitätsprüfung) ist eine Mindestanforderung. Meine Kunden wollen REST , aber ehrlich gesagt ist es für Hobbys.
Quarkly
2
@ Quarkly REST ist eher ein "Konzept" als ein "Standard". Es gibt nur sehr wenige Einschränkungen hinsichtlich der Art und Weise, wie Ressourcen übertragen werden sollen, und es wird nicht im Detail festgelegt, wie eine REST-API entworfen werden soll, so dass es dem Entwickler überlassen bleibt, wie er vorgehen soll. Es liegt an Ihrem Team / Unternehmen, die Regeln zu definieren, nach denen Sie Ihre REST-API betreiben dürfen. Es gibt einige Designrichtlinien, die "gut" und andere "schlecht" sind. Die Aussage, dass es sich um einen "beschissenen Standard" handelt, wird REST als Konzept / Philosophie jedoch nicht wirklich gerecht und ist etwas irreführend.
B12Toaster
1

Soweit ich das REST-Konzept verstehe, umfasst es eine Aktualisierung mehrerer Ressourcen mit einer Anforderung. Tatsächlich besteht der Trick hier darin, einen Container um diese mehreren Ressourcen herum anzunehmen und ihn als eine einzige Ressource zu betrachten. Sie können beispielsweise einfach davon ausgehen, dass die Liste der IDs eine Ressource identifiziert, die mehrere andere Ressourcen enthält.

In diesen Beispielen in Wikipedia wird auch über Ressourcen in Plural gesprochen.

Kris
quelle
1
Im Wikipedia-Beispiel weisen sie darauf hin, dass PUT und POST unterschiedlich behandelt werden und dass man POST lieber zum Schreiben einer oder mehrerer neuer Entitäten verwenden sollte.
Martin Kersten