Warum ein RESTful Design anstreben?
Die RESTful-Prinzipien bringen die Funktionen, die es Websites erleichtern (für einen zufälligen menschlichen Benutzer, sie zu "surfen"), in das API-Design der Webdienste ein , sodass sie für einen Programmierer einfach zu verwenden sind. REST ist nicht gut, weil es REST ist, es ist gut, weil es gut ist. Und es ist vor allem deshalb gut, weil es einfach ist .
Die Einfachheit von einfachem HTTP (ohne SOAP-Umschläge und überlastete Single-URI- POST
Dienste), was manche als "Mangel an Funktionen" bezeichnen , ist tatsächlich seine größte Stärke . HTTP fordert Sie von Anfang an auf Adressierbarkeit und Statuslosigkeit : Die beiden grundlegenden Entwurfsentscheidungen, mit denen HTTP auf die heutigen Mega-Sites (und Mega-Services) skalierbar bleibt.
Aber REST ist nicht das Beste: Manchmal kann ein RPC-Stil ("Remote Procedure Call" - wie SOAP) angebracht sein , und manchmal haben andere Anforderungen Vorrang vor den Vorzügen des Web. Das ist in Ordnung. Was wir nicht wirklich mögen, ist unnötige Komplexität . Zu oft bringt ein Programmierer oder ein Unternehmen RPC-ähnliche Dienste für einen Job ein, den einfaches altes HTTP problemlos verarbeiten kann. Der Effekt ist, dass HTTP für eine enorme XML-Nutzlast auf ein Transportprotokoll reduziert wird, das erklärt, was "wirklich" vor sich geht (nicht der URI oder die HTTP-Methode geben einen Hinweis darauf). Der resultierende Service ist viel zu komplex, kann nicht debuggt werden und funktioniert nur, wenn Ihre Clients genau das Setup haben, das der Entwickler beabsichtigt hat.
Ebenso wie ein Java / C # -Code nicht objektorientiert sein kann, macht die Verwendung von HTTP ein Design nicht RESTful. Man kann in der Eile gefangen sein, über ihre Dienste in Bezug auf Aktionen und entfernte Methoden nachzudenken , die aufgerufen werden sollten. Kein Wunder, dass dies meistens in einem RPC-Dienst (oder einem REST-RPC-Hybrid) endet. Der erste Schritt ist, anders zu denken. Ein RESTful-Design kann auf viele Arten erreicht werden. Eine Möglichkeit besteht darin , Ihre Anwendung in Bezug auf Ressourcen und nicht in Bezug auf Aktionen zu betrachten:
💡 Anstatt in Aktionen zu denken, die es ausführen kann ("Suche nach Orten auf der Karte") ...
... versuchen, in den Ergebnissen dieser Aktionen zu denken ("die Liste der Orte auf der Karte, die einem Suchkriterium entsprechen").
Ich werde unten Beispiele anführen. (Ein weiterer wichtiger Aspekt von REST ist die Verwendung von HATEOAS - ich putze es hier nicht, aber ich spreche in einem anderen Beitrag schnell darüber .)
Probleme des ersten Entwurfs
Werfen wir einen Blick auf das vorgeschlagene Design:
ACTION http://api.animals.com/v1/dogs/1/
Zunächst sollten wir nicht in Betracht ziehen, ein neues HTTP-Verb ( ACTION
) zu erstellen . Im Allgemeinen ist dies aus mehreren Gründen unerwünscht :
- (1) Woher weiß ein "zufälliger" Programmierer, wenn nur die Service-URI angegeben ist, dass das
ACTION
Verb existiert?
- (2) Wenn der Programmierer weiß, dass es existiert, wie wird er seine Semantik kennen? Was bedeutet dieses Verb?
- (3) Welche Eigenschaften (Sicherheit, Idempotenz) sollte man von diesem Verb erwarten?
- (4) Was ist, wenn der Programmierer einen sehr einfachen Client hat, der nur Standard-HTTP-Verben verarbeitet?
- (5) ...
Lassen Sie uns nun die Verwendung in Betracht ziehenPOST
(ich werde unten diskutieren, warum, nehmen Sie jetzt einfach mein Wort dafür):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
Dies könnte in Ordnung sein ... aber nur wenn :
{"action":"bark"}
war ein Dokument; und
/v1/dogs/1/
war ein "Dokumentenprozessor" (werkseitig) URI. Ein "Dokumentprozessor" ist eine URI, auf die Sie nur "werfen" und "vergessen" würden. Der Prozessor leitet Sie möglicherweise nach dem "Werfen" zu einer neu erstellten Ressource weiter. Beispiel: Der URI zum Posten von Nachrichten bei einem Nachrichtenbrokerdienst, der Sie nach dem Posten zu einem URI umleitet, der den Status der Nachrichtenverarbeitung anzeigt.
Ich weiß nicht viel über Ihr System, aber ich würde bereits wetten, dass beide nicht wahr sind:
{"action":"bark"}
ist kein Dokument , sondern die Methode, mit der Sie versuchen, sich in den Dienst einzuschleichen. und
- Die
/v1/dogs/1/
URI stellt eine "Hund" -Ressource dar (wahrscheinlich der Hund mit id==1
) und kein Dokumentenprozessor.
Alles was wir jetzt wissen ist, dass das obige Design nicht so RESTful ist, aber was ist das genau? Was ist daran so schlimm? Grundsätzlich ist es schlecht, weil das eine komplexe URI mit komplexen Bedeutungen ist. Daraus kann man nichts schließen. Wie würde ein Programmierer wissen, dass ein Hund eine bark
Aktion hat, die heimlich mit einem infundiert POST
werden kann?
Entwerfen der API-Aufrufe Ihrer Frage
Kommen wir also zur Sache und versuchen, diese Rinden RESTful zu gestalten, indem wir in Ressourcen denken . Gestatten Sie mir, das Buch Restful Web Services zu zitieren :
Eine POST
Anforderung ist ein Versuch, eine neue Ressource aus einer vorhandenen zu erstellen. Die vorhandene Ressource kann im Sinne einer Datenstruktur die übergeordnete Ressource der neuen Ressource sein, so wie die Wurzel eines Baums die übergeordnete Ressource aller seiner Blattknoten ist. Oder die vorhandene Ressource kann eine spezielle "Fabrik"
-Ressource sein, deren einziger Zweck darin besteht, andere Ressourcen zu generieren. Die zusammen mit einer POST
Anforderung gesendete Darstellung beschreibt den Anfangszustand der neuen Ressource. Wie bei PUT muss eine POST
Anfrage überhaupt keine Darstellung enthalten.
Nach der obigen Beschreibung können wir sehen, dass bark
dies als Unterressource von adog
modelliert werden kann (da a bark
in einem Hund enthalten ist, dh eine Rinde von einem Hund "gebellt" wird ).
Aus dieser Überlegung haben wir bereits:
- Die Methode ist
POST
- Die Ressource ist
/barks
Subressource of Dog : /v1/dogs/1/barks
, repräsentiert eine bark
"Fabrik". Diese URI ist für jeden Hund eindeutig (da sie unter ist /v1/dogs/{id}
).
Jetzt hat jeder Fall Ihrer Liste ein bestimmtes Verhalten.
1. bark sendet einfach eine E-Mail an dog.email
und zeichnet nichts auf.
Ist das Bellen (Senden einer E-Mail) eine synchrone oder asynchrone Aufgabe? Zweitens erfordert die bark
Anfrage ein Dokument (möglicherweise die E-Mail) oder ist es leer?
1.1 Rinde sendet eine E-Mail an dog.email
und zeichnet nichts auf (als synchrone Aufgabe)
Dieser Fall ist einfach. Ein Anruf bei der barks
Werksressource führt sofort zu einer Rinde (einer gesendeten E-Mail) und die Antwort (ob OK oder nicht) wird sofort gegeben:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Da es nichts aufzeichnet (ändert), 200 OK
ist genug. Es zeigt, dass alles wie erwartet gelaufen ist.
1.2 bark sendet eine E-Mail an dog.email
und zeichnet nichts auf (als asynchrone Aufgabe)
In diesem Fall muss der Client eine Möglichkeit haben, die bark
Aufgabe zu verfolgen . Die bark
Aufgabe sollte dann eine Ressource mit einer eigenen URI sein:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Auf diese Weise ist jeder bark
nachvollziehbar. Der Client kann dann eine GET
an die bark
URI senden, um den aktuellen Status zu ermitteln. Vielleicht sogar ein verwenden DELETE
, um es abzubrechen.
2. bark sendet eine E-Mail an dog.email
und erhöht sich dann dog.barkCount
um 1
Dies kann schwieriger sein, wenn Sie dem Client mitteilen möchten, dass die dog
Ressource geändert wird:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
In diesem Fall soll der location
Header den Client wissen lassen, dass er einen Blick darauf werfen soll dog
. Aus dem HTTP-RFC über303
:
Diese Methode dient hauptsächlich dazu, dass die Ausgabe eines
POST
aktivierten Skripts den Benutzeragenten zu einer ausgewählten Ressource umleitet.
Wenn die Aufgabe asynchron ist, bark
wird genau wie in der 1.2
Situation eine Unterressource benötigt, 303
die nach Abschluss GET .../barks/Y
der Aufgabe zurückgegeben werden sollte.
3. Rinde erstellt einen neuen " bark
" Datensatz mit bark.timestamp
Aufzeichnung, wenn die Rinde aufgetreten ist. Es wird auch dog.barkCount
um 1 erhöht .
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Hier bark
wird das aufgrund der Anforderung erstellt, sodass der Status 201 Created
angewendet wird.
Wenn die Erstellung asynchron ist, 202 Accepted
ist stattdessen a erforderlich ( wie im HTTP-RFC angegeben ).
Der gespeicherte Zeitstempel ist Teil der bark
Ressource und kann mit einem darauf abgerufen werden GET
. Der aktualisierte Hund kann auch darin "dokumentiert" werden GET dogs/X/barks/Y
.
4. bark führt einen Systembefehl aus, um die neueste Version des Hundecodes von Github abzurufen. Anschließend wird eine Textnachricht gesendet, in dog.owner
der ihnen mitgeteilt wird, dass der neue Hundecode in Produktion ist.
Der Wortlaut dieses Artikels ist kompliziert, aber es ist so ziemlich eine einfache asynchrone Aufgabe:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Der Client würde dann GET
s ausgeben /v1/dogs/1/barks/a65h44
, um den aktuellen Status zu erfahren (wenn der Code abgerufen wurde, wurde die E-Mail an den Eigentümer gesendet und so weiter). Wann immer sich der Hund ändert, 303
ist a anwendbar.
Einpacken
Zitat von Roy Fielding :
Das einzige, was REST von Methoden verlangt, ist, dass sie für alle Ressourcen einheitlich definiert sind (dh dass Vermittler den Ressourcentyp nicht kennen müssen, um die Bedeutung der Anforderung zu verstehen).
In den obigen Beispielen POST
ist einheitlich ausgelegt. Es wird den Hund " bark
" machen. Das ist weder sicher (was bedeutet, dass Rinde Auswirkungen auf die Ressourcen hat) noch idempotent (jede Anfrage ergibt eine neue bark
), was gut zum POST
Verb passt .
Ein Programmierer würde wissen: a, POST
um a zu barks
ergeben bark
. Die Antwortstatuscodes (bei Bedarf auch mit Entity-Body und Headern) erläutern, was sich geändert hat und wie der Client vorgehen kann und sollte.
Hinweis: Die wichtigsten verwendeten Quellen waren: " Restful Web Services " -Buch, HTTP RFC und Roy Fieldings Blog .
Bearbeiten:
Die Frage und damit die Antwort haben sich seit ihrer Erstellung erheblich geändert. Die ursprüngliche Frage zum Design einer URI lautet wie folgt:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Nachfolgend finden Sie eine Erklärung, warum dies keine gute Wahl ist:
Wie Clients dem Server mitteilen, was mit den Daten zu tun ist, sind die Methodeninformationen .
- RESTful-Webdienste übertragen Methodeninformationen in der HTTP-Methode.
- Typische RPC-Style- und SOAP-Services behalten ihre im Entity-Body und im HTTP-Header.
Welcher Teil der Daten [der Client möchte, dass der Server] bearbeitet wird, sind die Scoping-Informationen .
- RESTful-Services verwenden den URI. SOAP / RPC-artige Dienste verwenden erneut die Entity-Body- und HTTP-Header.
Nehmen Sie als Beispiel den URI von Google http://www.google.com/search?q=DOG
. Dort sind die Methodeninformationen GET
und die Scoping-Informationen /search?q=DOG
.
Um es kurz zu machen:
- In RESTful-Architekturen gehen die Methodeninformationen in die HTTP-Methode ein.
- In ressourcenorientierten Architekturen werden die Bereichsinformationen in die URI eingegeben.
Und die Faustregel:
Wenn die HTTP-Methode nicht mit den Methodeninformationen übereinstimmt, ist der Dienst nicht RESTful. Wenn sich die Scoping-Informationen nicht in der URI befinden, ist der Service nicht ressourcenorientiert.
Sie können die "Rinde" "Aktion" in die URL (oder in den Entity-Body) einfügen und verwenden POST
. Kein Problem, es funktioniert und ist vielleicht der einfachste Weg, dies zu tun, aber dies ist nicht RESTful .
Um Ihren Service wirklich RESTful zu halten, müssen Sie möglicherweise einen Schritt zurücktreten und überlegen, was Sie hier wirklich tun möchten (welche Auswirkungen dies auf die Ressourcen haben wird).
Ich kann nicht über Ihre spezifischen Geschäftsanforderungen sprechen, aber ich möchte Ihnen ein Beispiel geben: Betrachten Sie einen RESTful-Bestellservice, bei dem Bestellungen wie bei URIs erfolgen example.com/order/123
.
Angenommen, wir möchten eine Bestellung stornieren. Wie machen wir das? Man könnte versucht sein zu glauben, dass dies eine "Stornierung", "Aktion" ist, und sie als zu gestalten POST example.com/order/123?do=cancel
.
Das ist nicht RESTful, wie wir oben gesprochen haben. Stattdessen könnten wir PUT
eine neue Darstellung von order
mit einem canceled
Element senden, das an true
folgende Adresse gesendet wird :
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
Und das ist es. Wenn die Bestellung nicht storniert werden kann, kann ein bestimmter Statuscode zurückgegeben werden. (Der Einfachheit halber kann auch ein Subressourcen-Design wie POST /order/123/canceled
beim Entity-Body true
verfügbar sein.)
In Ihrem speziellen Szenario können Sie etwas Ähnliches ausprobieren. Auf diese Weise könnte ein GET
at , während beispielsweise ein Hund bellt, /v1/dogs/1/
diese Informationen enthalten (z<barking>true</barking>
. B. ) . Oder ... wenn das zu kompliziert ist, lockern Sie Ihre RESTful-Anforderung und bleiben Sie dabei POST
.
Aktualisieren:
Ich möchte die Antwort nicht zu groß machen, aber es dauert eine Weile, bis ich den Dreh raus habe, einen Algorithmus (eine Aktion ) als eine Reihe von Ressourcen verfügbar zu machen. Anstatt in Aktionen zu denken ( "Suche nach Orten auf der Karte" ), muss man in den Ergebnissen dieser Aktion denken ( "die Liste der Orte auf der Karte, die einem Suchkriterium entsprechen" ).
Möglicherweise kehren Sie zu diesem Schritt zurück, wenn Sie feststellen, dass Ihr Design nicht zur einheitlichen HTTP-Oberfläche passt.
Query - Variablen sind Scoping Informationen , aber nicht bezeichnen neue Ressourcen ( /post?lang=en
ist eindeutig die gleiche Ressource wie /post?lang=jp
, nur eine andere Darstellung). Sie werden vielmehr verwendet, um den Client-Status (z. B. ?page=10
damit der Status nicht auf dem Server gespeichert wird; dies ?lang=en
ist auch hier ein Beispiel) oder Eingabeparameter für algorithmische Ressourcen ( /search?q=dogs
, /dogs?code=1
) zu übermitteln . Wieder keine unterschiedlichen Ressourcen.
Eigenschaften von HTTP-Verben (Methoden):
Ein weiterer klarer Punkt, der ?action=something
in der URI angezeigt wird, ist nicht RESTful. Dies sind die Eigenschaften von HTTP-Verben:
GET
und HEAD
sind sicher (und idempotent);
PUT
und DELETE
sind nur idempotent;
POST
ist weder.
Sicherheit : Eine GET
oder HEAD
Anforderung ist eine Anforderung zum Lesen einiger Daten, keine Anforderung zum Ändern eines Serverstatus. Der Client kann 10 Mal eine GET
oder eine HEAD
Anfrage stellen, und es ist dasselbe wie einmal oder gar nicht .
Idempotenz : Eine idempotente Operation in einer, die den gleichen Effekt hat, unabhängig davon, ob Sie sie einmal oder mehrmals anwenden (in der Mathematik ist das Multiplizieren mit Null idempotent). Wenn Sie DELETE
eine Ressource einmal haben, hat das erneute Löschen den gleichen Effekt (die Ressource ist GONE
bereits vorhanden).
POST
ist weder sicher noch idempotent. Wenn Sie zwei identische POST
Anforderungen an eine 'Factory'-Ressource stellen, werden wahrscheinlich zwei untergeordnete Ressourcen dieselbe Information enthalten. Bei Überlastung (Methode in URI oder Entity-Body) POST
sind alle Wetten deaktiviert.
Beide Eigenschaften waren wichtig für den Erfolg des HTTP-Protokolls (über unzuverlässige Netzwerke!): Wie oft haben Sie GET
die Seite aktualisiert ( ), ohne zu warten, bis sie vollständig geladen ist?
Das Erstellen und Einfügen einer Aktion in die URL bricht eindeutig den Vertrag der HTTP-Methoden. Die Technologie ermöglicht es Ihnen erneut, dies zu tun, aber das ist kein RESTful-Design.