RESTFul: Zustandsänderungsaktionen

59

Ich plane, eine RESTfull-API zu erstellen, aber es gibt einige architektonische Fragen, die Probleme in meinem Kopf verursachen. Das Hinzufügen von Back-End-Geschäftslogik zu Clients ist eine Option, die ich vermeiden möchte, da das Aktualisieren mehrerer Clientplattformen in Echtzeit schwierig ist, wenn sich die Geschäftslogik schnell ändern kann.

Nehmen wir an, wir haben Artikel als Ressource (API / Artikel). Wie sollten wir Aktionen wie Veröffentlichen, Aufheben der Veröffentlichung, Aktivieren oder Deaktivieren usw. implementieren, aber versuchen, dies so einfach wie möglich zu halten?

1) Sollten wir api / article / {id} / {action} verwenden, da dort eine Menge Backend-Logik vorkommen kann, z. B. das Verschieben an entfernte Standorte oder das Ändern mehrerer Eigenschaften. Das Schwierigste hierbei ist wahrscheinlich, dass wir alle Artikeldaten zur Aktualisierung an die API zurücksenden müssen und die Mehrbenutzerarbeit nicht implementiert werden konnte. Zum Beispiel könnte der Redakteur 5 Sekunden ältere Daten senden und die Korrektur überschreiben, die ein anderer Journalist gerade vor 2 Sekunden vorgenommen hat, und ich kann dies den Kunden auf keinen Fall erklären, da diejenigen, die einen Artikel veröffentlichen, in keiner Weise mit der Aktualisierung des Inhalts verbunden sind.

2) Das Erstellen einer neuen Ressource kann auch eine Option sein, api / article- {action} / id, aber dann wäre die zurückgegebene Ressource nicht article- {action}, sondern article, bei dem ich nicht sicher bin, ob dies richtig ist. Auch im serverseitigen Code behandelt die Artikelklasse die aktuelle Arbeit an beiden Ressourcen, und ich bin mir nicht sicher, ob dies gegen das REST-Denken verstößt

Anregungen sind willkommen ..

Miro Svrtan
quelle
Es ist vollkommen legal, dass "Aktionen" Teil einer RESTful-URI sind - wenn sie eine auszuführende Aktion / einen auszuführenden Algorithmus angeben. Was ist los mit api/article?action=publish? Abfrageparameter sind für solche Fälle vorgesehen, in denen der Status der Ressource von dem von Ihnen genannten 'Algorithmus' (oder der Aktion) abhängt. ZB api/articles?sort=ascist gültig
PhD
1
Ich schlage vor , Sie zu prüfen, diese Zuschreibung, die Sie mit einer noch begeistern kann mehr RESTful Lösung.
Benjol
Eines der Probleme, die ich mit api / article? Action = publish sehe, ist, dass in der REST-Anwendung ALLE Artikeldaten zum Veröffentlichen gesendet werden sollen, während ich dies lieber mache: api / article / 4545 / publish / ohne zusätzliche
Angaben
2
Hervorragende Ressource zu diesem Thema und mehr: REST-API-Design - Ressourcenmodellierung
Izhaki
@PhD: Während es im Protokoll vollkommen legal ist, sollten URIs im Allgemeinen Substantive und keine Verben sein. Ein Verb in der URI zu haben, ist normalerweise ein Zeichen für ein schlechtes REST-Design.
Lie Ryan

Antworten:

49

Ich finde die hier beschriebenen Praktiken hilfreich:

Was ist mit Aktionen, die nicht in die Welt der CRUD-Operationen passen?

Hier können die Dinge verschwimmen. Es gibt eine Reihe von Ansätzen:

  1. Strukturieren Sie die Aktion so, dass sie wie ein Feld einer Ressource angezeigt wird. Dies funktioniert, wenn die Aktion keine Parameter übernimmt. Beispielsweise könnte eine Aktivierungsaktion einem Booleschen activatedFeld zugeordnet und über einen PATCH auf die Ressource aktualisiert werden.
  2. Behandle es wie eine Subressource mit REST-Prinzipien. Zum Beispiel kann GitHub API Sie star einen Kerns mit PUT /gists/:id/starund Aufheben der Markierung mit DELETE /gists/:id/star.
  3. Manchmal hat man wirklich keine Möglichkeit, die Aktion auf eine vernünftige RESTful-Struktur abzubilden. Beispielsweise ist eine Suche mit mehreren Ressourcen nicht sinnvoll, um auf den Endpunkt einer bestimmten Ressource angewendet zu werden. In diesem Fall /searchwäre es am sinnvollsten, auch wenn es sich nicht um eine Ressource handelt. Dies ist in Ordnung - tun Sie einfach, was aus Sicht des API-Verbrauchers richtig ist, und stellen Sie sicher, dass es klar dokumentiert ist, um Verwirrung zu vermeiden.
Tim
quelle
Ich stimme für den Ansatz 2. Während es für API-Aufrufer nach schwerfälligen künstlichen Ressourcen aussieht, wissen sie in Wirklichkeit nicht, was auf dem Server geschieht. Wenn ich POST anrufe /article/123/deactivations, um eine neue Deaktivierungsanforderung für den Artikel 123 zu erstellen, deaktiviert der Server möglicherweise nicht nur die angeforderte Ressource, sondern speichert meine Deaktivierungsanforderung tatsächlich, damit ich ihren Status später abrufen kann.
JustAMartin
2
warum PUT /gists/:id/star nicht POST /gists/:id/star?
Filip Bartuzi
9
@FilipBartuzi Weil PUT idempotent ist - das heißt, egal wie oft Sie eine Aktion mit denselben Parametern ausführen, das Ergebnis ist immer dasselbe (z. B. wenn Sie ein Licht von aus nach ein schalten, ändert es sich. Wenn Sie versuchen, es einzuschalten es wieder an, ändert sich nichts - es ist schon an). POST ist nicht idempotent - das heißt, jedes Mal, wenn Sie eine Aktion ausführen, auch wenn dieselben Parameter verwendet werden, hat die Aktion ein anderes Ergebnis (z. B. wenn Sie einen Brief an jemanden senden, hat diese Person einen Brief erhalten. Wenn Sie einen identischen Brief an senden die gleiche Person, sie haben jetzt 2 Buchstaben).
Raphael
9

Vorgänge, die zu wesentlichen Zustands- und Verhaltensänderungen auf der Serverseite führen, wie die von Ihnen beschriebene "Publizieren" -Aktion, sind in REST nur schwer explizit zu modellieren. Eine Lösung, die ich oft sehe, besteht darin, ein derart komplexes Verhalten implizit durch Daten zu steuern.

Erwägen Sie, Waren über eine REST-API zu bestellen, die von einem Online-Händler bereitgestellt wird. Bestellung ist ein komplexer Vorgang. Mehrere Produkte werden verpackt und versendet, Ihr Konto wird belastet und Sie erhalten eine Quittung. Sie können Ihre Bestellung für eine begrenzte Zeit stornieren und es gibt natürlich eine vollständige Geld-zurück-Garantie, mit der Sie Produkte gegen eine Rückerstattung zurücksenden können.

Anstelle eines komplexen Einkaufsvorgangs können Sie mit einer solchen API möglicherweise eine neue Ressource, eine Bestellung, erstellen. Zu Beginn können Sie beliebige Änderungen daran vornehmen: Hinzufügen oder Entfernen von Produkten, Ändern der Lieferadresse, Auswählen einer anderen Zahlungsoption oder Stornieren Ihrer Bestellung insgesamt. Sie können all dies tun, weil Sie noch nichts gekauft haben. Sie manipulieren nur einige Daten auf dem Server.

Sobald Ihre Bestellung abgeschlossen ist und Ihre Nachfrist abgelaufen ist, sperrt der Server Ihre Bestellung, um weitere Änderungen zu verhindern. Erst zu diesem Zeitpunkt beginnt die komplexe Abfolge von Vorgängen, die Sie jedoch nicht direkt steuern können, sondern nur indirekt über die Daten, die Sie zuvor in die Bestellung eingegeben haben.

Basierend auf Ihrer Beschreibung könnte "Publizieren" auf diese Weise implementiert werden. Anstatt eine Operation verfügbar zu machen, platzieren Sie eine Kopie des geprüften Entwurfs und möchten ihn unter / publish als neue Ressource veröffentlichen. Dadurch wird sichergestellt, dass spätere Aktualisierungen des Entwurfs nicht veröffentlicht werden, selbst wenn der Veröffentlichungsvorgang selbst Stunden später abgeschlossen wird.

Ferenc Mihaly
quelle
Die Idee, die gesamte Ressource von einem unveröffentlichten Artikel auf einen Entwurf umzustellen, würde genau zu diesem Fall passen, würde aber nicht zu allen anderen Aktionen, die für eine Ressource im Allgemeinen existieren. Soll REST überhaupt damit umgehen? Vielleicht missbrauche ich es und sollte es nur als CRUD und nicht mehr verwenden, wie SQL-Abfragen, bei denen ich keine Logik innerhalb der Abfrage selbst erwarte.
Miro Svrtan
Warum frage ich das alles? Nun, seit einiger Zeit beginnen Web-Apps, sich zu vervielfachen, und ich würde es vorziehen, viel Geschäftslogik auf dem Server zu belassen, da die Aktualisierung der Geschäftslogik auf iOS, Android, Web, Desktop oder einer anderen Plattform, die mir in den Sinn kommt, ziemlich unmöglich wird schnell und ich möchte alle Probleme der Abwärtskompatibilität vermeiden, wenn ich ein kleines Stück BL ändere.
Miro Svrtan
2
Ich denke, REST kann gut mit Geschäftslogik umgehen, aber es ist nicht so geeignet, um vorhandene Geschäftslogik, die ohne REST geschrieben wurde, verfügbar zu machen. Aus diesem Grund stellen viele Unternehmen wie Microsoft, SAP und andere häufig nur Daten mit CRUD-Operationen zur Verfügung, wie Sie bereits sagten. Schauen Sie sich das Open Data-Protokoll an, um zu sehen, wie es funktioniert.
Ferenc Mihaly
7

Wir müssen alle Artikeldaten zur Aktualisierung an die API zurücksenden, und die Mehrbenutzerarbeit konnte nicht implementiert werden. Zum Beispiel könnte der Redakteur 5 Sekunden ältere Daten senden und die Korrektur überschreiben, die ein anderer Journalist gerade vor 2 Sekunden vorgenommen hat, und ich kann dies den Kunden auf keinen Fall erklären, da diejenigen, die einen Artikel veröffentlichen, in keiner Weise mit der Aktualisierung des Inhalts verbunden sind.

Dies ist eine Herausforderung, egal was Sie tun. Es ist ein sehr ähnliches Problem wie bei der verteilten Quellcodeverwaltung (Quecksilber, Git usw.), und die in HTTP / ReST geschriebene Lösung sieht ein bisschen ähnlich aus.

Angenommen, Sie haben zwei Benutzer, Alice und Bob, an denen beide arbeiten /articles/lunch. (Zur Verdeutlichung ist die Antwort fett gedruckt.)

Zunächst erstellt Alice den Artikel.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

Der Server hat keine Ressource erstellt, da an die Anforderung keine "Version" angehängt war (unter der Annahme eines Bezeichners von /articles/{id}/{version}. Um die Erstellung durchzuführen, wurde Alice an die URL des Artikels / der Version weitergeleitet, den / die sie erstellen wird. Benutzer von Alice Der Agent wendet die Anforderung dann erneut an der neuen Adresse an.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

Und jetzt wurde der Artikel erstellt. Als nächstes schaut Bob sich den Artikel an:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob schaut dorthin:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Er beschließt, sein eigenes Wechselgeld hinzuzufügen.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Wie bei Alice wird Bob an den Ort weitergeleitet, an dem er eine neue Version erstellen wird.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Schließlich entscheidet Alice, dass sie ihren eigenen Artikel hinzufügen möchte:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Anstatt wie gewohnt umgeleitet zu werden, wird ein anderer Statuscode an den Client zurückgegeben, 409der Alice mitteilt, dass die Version, von der sie abzweigen wollte, bereits verzweigt wurde. Die neuen Ressourcen wurden trotzdem erstellt (wie in der LocationKopfzeile gezeigt), und die Unterschiede zwischen den beiden wurden in den Antworttext aufgenommen. Alice weiß jetzt, dass die Anfrage, die sie gerade gestellt hat, irgendwie zusammengeführt werden muss.


All diese Umleitungen hängen mit der Semantik von zusammen PUT, die es erforderlich macht, dass neue Ressourcen genau dort erstellt werden, wo die Anforderungszeile dies verlangt. Dies könnte POSTstattdessen auch einen Anforderungszyklus mit speichern , aber dann müsste die Versionsnummer in der Anforderung durch eine andere Magie codiert werden, die mir zum Zwecke der Veranschaulichung weniger offensichtlich erschien, in einer echten API jedoch wahrscheinlich immer noch bevorzugt wird um Anforderungs- / Antwortzyklen zu minimieren.

SingleNegationElimination
quelle
1
Versoning war hier kein Thema, ich sagte es nur als Beispiel für mögliche Probleme, wenn Artikel als Ressource für Veröffentlichungsaktionen verwendet werden
Miro Svrtan
3

Hier ist ein weiteres Beispiel, das sich nicht mit Dokumenteninhalten befasst, sondern eher mit vorübergehenden Zuständen. (Ich finde die Versionierung - da im Allgemeinen jede Version eine neue Ressource sein kann - eine Art einfaches Problem.)

Angenommen, ich möchte einen Dienst, der auf einem Computer ausgeführt wird, über einen REST verfügbar machen, damit er gestoppt, gestartet, neu gestartet und so weiter werden kann.

Was ist hier der RESTVOLLSTE Ansatz? POST / service? Command = restart zum Beispiel? Oder POST / service / state mit einem Körper von 'running'?

Es wäre schön, hier Best Practices zu kodifizieren und zu prüfen, ob REST der richtige Ansatz für diese Art von Situation ist.

Zweitens nehmen wir an, dass ich eine Aktion von einem Dienst aus ausführen möchte, die sich nicht auf den eigenen Status auswirkt, sondern einen Nebeneffekt auslöst. Zum Beispiel ein Mail-Dienst, der einen zum Zeitpunkt des Anrufs erstellten Bericht an eine Reihe von E-Mail-Adressen sendet.

GET / report ist möglicherweise eine Möglichkeit, eine Kopie des Berichts selbst zu erhalten. aber was ist, wenn wir weitere Aktionen wie E-Mail, wie ich oben sage, auf den Server pushen möchten. Oder in eine Datenbank schreiben.

In diesen Fällen geht es um Ressourcenteilung, und ich sehe Möglichkeiten, sie auf REST-orientierte Weise zu behandeln, aber ehrlich gesagt fühlt es sich ein bisschen nach Hack an, dies zu tun. Vielleicht ist die Schlüsselfrage, ob eine REST-API im Allgemeinen Nebenwirkungen unterstützen sollte.

AMD
quelle
2

REST ist datenorientiert und als solche funktionieren Ressourcen am besten als "Dinge", nicht als Aktionen. Die implizite Semantik von http-Methoden; GET, PUT, DELETE usw. dienen dazu, die Orientierung zu verstärken. POST ist natürlich die Ausnahme.

Eine Ressource kann eine Mischung aus Daten sein, z. Artikelinhalt; und Metadaten dh. veröffentlicht, gesperrt, Revision. Es gibt viele andere Möglichkeiten, die Daten aufzuteilen, aber Sie müssen zuerst durchgehen, wie der Datenfluss aussehen wird, um den optimalen zu bestimmen (falls es einen gibt). Zum Beispiel kann es sein, dass Revisionen ihre eigene Ressource unter dem Artikel sein sollten, wie TokenMacGuy vorschlägt.

In Bezug auf die Implementierung würde ich wahrscheinlich etwas tun, was TockenMacGuy vorschlägt. Ich würde auch ein Metadatenfeld zum Artikel hinzufügen, nicht zur Revision, wie 'gesperrt' und 'veröffentlicht'.

dietbuddha
quelle
1

Betrachten Sie es nicht als direkte Manipulation des Status des Artikels. Stattdessen geben Sie einen Änderungsauftrag ein , um die Erstellung des Artikels anzufordern.

Sie können das Einfügen eines Änderungsauftrags als das Erstellen einer neuen Änderungsauftragsressource (POST) modellieren. Es gibt viele Vorteile. Sie können beispielsweise ein zukünftiges Datum und eine zukünftige Uhrzeit angeben, zu der der Artikel als Teil des Änderungsauftrags veröffentlicht werden soll, und den Server sich Gedanken darüber machen lassen, wie dies implementiert wird.

Wenn das Veröffentlichen kein sofortiger Vorgang ist, müssen Sie nicht warten, bis der Vorgang abgeschlossen ist, bevor Sie zum Client zurückkehren. Sie bestätigen lediglich, dass der Änderungsauftrag erstellt wurde, und geben die Änderungsauftrags-ID zurück. Sie können dann die dieser Änderungsreihenfolge entsprechende URL verwenden, um den Status der Änderungsreihenfolge mitzuteilen.

Eine wichtige Erkenntnis für mich war, dass das Erkennen dieser Metapher für die Änderungsreihenfolge nur ein weiterer Weg ist, um die objektorientierte Programmierung zu beschreiben. Anstelle von Ressourcen nennen wir dann Objekte. Anstelle von Änderungsaufträgen nennen wir sie Nachrichten. Eine Möglichkeit, eine Nachricht von A nach B in OO zu senden, besteht darin, dass A eine Methode auf B aufruft Senden Sie es an B. REST formalisiert diesen Prozess einfach.

Patrick McElhaney
quelle
Ich würde dies tatsächlich näher am Actor-Modell als an OO sehen. Wenn Sie REST-Anforderungen als Nachrichten betrachten (die sie sind), werden sie sehr genau auf Event Sourcing ausgerichtet, um den Status zu verwalten. REST unterteilt seine Interaktionen in CQRS-Linien. GET-Nachrichten, bei denen Abfrage, POST, PUT, PATCH, DELETE in den Befehlsbereich fallen.
WillD
0

Wenn ich Sie richtig verstehe, ist das, was Sie haben, meines Erachtens eher ein Problem mit der Bestimmung von Geschäftsregeln als ein technisches Problem.

Die Tatsache, dass ein Artikel überschrieben werden kann, kann durch die Einführung von Berechtigungsstufen behoben werden, bei denen ältere Benutzer Versionen von jüngeren Benutzern überschreiben können. Außerdem können Versionen sowie eine Spalte zum Erfassen des Status des Artikels (z. B. "in Entwicklung", "endgültig") eingeführt werden. usw.), könnten Sie dies überwinden. Sie können dem Benutzer auch die Möglichkeit geben, eine bestimmte Version entweder nach einer Kombination aus dem Zeitpunkt der Übermittlung und der Versionsnummer auszuwählen.

In allen oben genannten Fällen muss Ihr Dienst die von Ihnen festgelegten Geschäftsregeln implementieren. Sie können den Service also mit den folgenden Parametern aufrufen: Benutzer-ID, Artikel, Version, Aktion (wobei die Version optional ist, hängt dies wiederum von Ihren Geschäftsregeln ab).

Keine Chance
quelle
Ich glaube nicht, dass dies eine Geschäftsregel ist, sondern eine rein technische. Die Idee, eine Version hinzuzufügen, ist eine gute Idee, um das Überschreiben von Regeln zu erleichtern, löst aber immer noch nicht die Situation, dass das Aktualisieren von Inhalten und das Veröffentlichen von Inhalten keine verwandten Aktionen sind.
Miro Svrtan