Ich habe drei Fragen zum REST-API-Design, von denen ich hoffe, dass jemand etwas Licht ins Dunkel bringen kann. Ich habe viele Stunden unermüdlich gesucht, aber nirgendwo Antworten auf meine Fragen gefunden (vielleicht weiß ich einfach nicht, wonach ich suchen soll?).
Frage 1
Meine erste Frage hat mit Aktionen / RPC zu tun. Ich habe eine Weile eine REST-API entwickelt und bin es gewohnt, über Sammlungen und Ressourcen nachzudenken. Ich bin jedoch auf einige Fälle gestoßen, in denen das Paradigma nicht zuzutreffen scheint, und ich frage mich, ob es eine Möglichkeit gibt, dies mit dem REST-Paradigma in Einklang zu bringen.
Insbesondere habe ich einen Fall, in dem das Ändern einer Ressource dazu führt, dass eine E-Mail generiert wird. Zu einem späteren Zeitpunkt kann der Benutzer jedoch ausdrücklich angeben, dass er die zuvor gesendete E-Mail erneut senden möchte. Beim erneuten Senden der E-Mail wird keine Ressource geändert. Es wird kein Status geändert. Es ist einfach eine Aktion, die stattfinden muss. Die Aktion ist an den jeweiligen Ressourcentyp gebunden.
Ist es angemessen, eine Art Aktionsaufruf mit einer Ressourcen-URI (z. B. /collection/123?action=resendEmail
) zu mischen ? Wäre es besser, die Aktion anzugeben und die Ressourcen-ID an sie zu übergeben (z. B. /collection/resendEmail?id=123
)? Ist das der falsche Weg? Traditionell (zumindest bei HTTP) ist die ausgeführte Aktion die Anforderungsmethode (GET, POST, PUT, DELETE), aber diese erlauben keine benutzerdefinierten Aktionen mit einer Ressource.
Frage 2
Ich verwende den Querystring-Teil der URL, um den Satz von Ressourcen zu filtern, der beim Abfragen einer Sammlung zurückgegeben wird (z /collection?someField=someval
. B. ). In meinem API-Controller bestimme ich dann, welche Art von Vergleich mit diesem Feld und Wert durchgeführt wird. Ich habe festgestellt, dass das wirklich nicht funktioniert. Ich benötige eine Möglichkeit, dem API-Benutzer zu ermöglichen, die Art des Vergleichs anzugeben, den er durchführen möchte.
Die beste Idee, die ich mir bisher ausgedacht habe, besteht darin, dem API-Benutzer zu erlauben, sie als Anhang zum Feldnamen anzugeben (z. B. /collection?someField:gte=someval
um anzugeben, dass Ressourcen zurückgegeben werden sollen, die someField
größer oder gleich (> =) someval
sind Ist dies eine gute Idee? Eine schlechte Idee? Wenn ja, warum? Gibt es eine bessere Möglichkeit, dem Benutzer zu ermöglichen, die Art des Vergleichs anzugeben, der mit dem angegebenen Feld und Wert durchgeführt werden soll?
Frage 3
Ich sehe oft URIs, die so aussehen /person/123/dogs
, als würden sie die person
s bekommen dogs
. Ich habe so etwas im Allgemeinen vermieden, weil ich am Ende herausfinde, dass Sie durch das Erstellen eines solchen URI tatsächlich nur auf eine dogs
Sammlung zugreifen, die nach einer bestimmten person
ID gefiltert ist . Es wäre gleichbedeutend mit /dogs?person=123
. Gibt es jemals wirklich einen guten Grund dafür, dass ein REST-URI mehr als zwei Ebenen tief ist ( /collection/resource_id
)?
Antworten:
Ich würde das lieber anders modellieren, mit einer Sammlung von Ressourcen, die die E-Mails darstellen, die gesendet werden sollen. Das Senden wird zu gegebener Zeit von den Interna des Dienstes verarbeitet. Zu diesem Zeitpunkt wird die entsprechende Ressource entfernt. (Oder der Benutzer könnte die Ressource vorzeitig LÖSCHEN, wodurch die Anforderung zum Senden abgebrochen wird.)
Was auch immer Sie tun, setzen Sie keine Verben in den Ressourcennamen! Das ist das Substantiv (und der Abfrageteil ist die Menge der Adjektive). Nomening Verben seltsam REST!
Ich möchte lieber eine allgemeine Filterklausel angeben und diese als optionalen Abfrageparameter für jede Anforderung zum Abrufen des Inhalts der Sammlung verwenden. Der Client kann dann genau angeben, wie der zurückgegebene Satz nach Belieben eingeschränkt werden soll. Ich würde mir auch ein bisschen Sorgen um die Auffindbarkeit der Filter- / Abfragesprache machen. Je reicher Sie es machen, desto schwieriger ist es für beliebige Kunden, es zu entdecken. Ein alternativer Ansatz, der sich zumindest theoretisch mit diesem Problem der Auffindbarkeit befasst, besteht darin, die Erstellung von Restriktionsunterressourcen der Sammlung zu ermöglichen, die Clients durch POSTing eines Dokuments erhalten, das die Einschränkung der Sammlungsressource beschreibt. Es ist immer noch ein leichter Missbrauch, aber zumindest einer, den Sie eindeutig auffindbar machen können!
Diese Art der Auffindbarkeit ist eines der Dinge, die ich bei REST am wenigsten stark finde.
Wenn die verschachtelte Sammlung wirklich ein Untermerkmal der Mitgliedsentitäten der äußeren Sammlung ist, ist es sinnvoll, sie als Unterressource zu strukturieren. Mit "Untermerkmal" meine ich so etwas wie eine UML-Zusammensetzungsbeziehung, bei der die Zerstörung der äußeren Ressource natürlich die Zerstörung der inneren Sammlung bedeutet.
Andere Arten von Sammlungen können als HTTP-Umleitung modelliert werden. Auf diese Weise
/person/123/dogs
kann in der Tat mit einem 307 reagiert werden, der auf umleitet/dogs?person=123
. In diesem Fall handelt es sich bei der Sammlung nicht um eine UML-Komposition, sondern um eine UML-Aggregation. Der Unterschied ist wichtig; es ist bedeutend!quelle
resendEmail
Obwohl die Aktion durch Erstellen einer Sammlung und POSTing ausgeführt werden kann, scheint dies weniger natürlich zu sein. Tatsächlich speichere ich nichts in der Datenbank, wenn eine E-Mail erneut gesendet wird (keine Notwendigkeit). Es wird keine Ressource geändert, daher handelt es sich lediglich um eine Aktion, die entweder erfolgreich ist oder fehlschlägt. Ich konnte keine Ressourcen-ID zurückgeben, die über die Laufzeit des Aufrufs hinaus existiert, sodass eine solche Implementierung ein Hack ist, anstatt RESTful zu sein. Es ist einfach keine CRUD-Operation.Es ist verständlich, ein wenig verwirrt darüber zu sein, wie REST richtig eingesetzt wird, basierend auf all den Möglichkeiten, wie ich große Unternehmen beim Entwurf ihrer REST-APIs gesehen habe.
Sie haben Recht, dass REST ein Ressourcensammelsystem ist. Es steht für Representational State Transfer. Keine gute Definition, wenn Sie mich fragen. Die Hauptkonzepte sind jedoch die 4 HTTP-VERBs, die zustandslos sind.
Das Wichtige ist, dass Sie nur 4 VERBS mit REST haben. Dies sind GET, POST, PUT und DELETE. Ihr
resend
Beispiel wäre das Hinzufügen eines neuen Verbs zu REST. Dies sollte eine rote Fahne sein.Frage 1
Es ist wichtig zu wissen, dass der Aufrufer Ihrer REST-API nicht wissen muss, dass das Ausführen einer
PUT
für Ihre Sammlung dazu führen würde, dass eine E-Mail generiert wird. Das riecht nach einem Leck für mich. Was sie wissen könnten, ist, dass das Ausführen von aPUT
zu zusätzlichen Aufgaben führen könnte, die sie später abfragen könnten. Sie würden dies wissen, indem sie eineGET
für die kürzlich erstellte Ressource ausführen . DasGET
zurückkehren würde die Ressource und alle derTask
Ressource - ID ist mit ihm verbunden. Sie können diese Aufgaben später abfragen, um ihren Status zu ermitteln und sogar eine neue Aufgabe einzureichenTask
.Sie haben einige Möglichkeiten.
REST - Aufgabenressourcenbasierter Ansatz
Erstellen Sie eine
tasks
Ressource, in der Sie bestimmte Aufgaben an Ihr System senden können, um Aktionen auszuführen. Sie können dannGET
die Aufgabe basierend auf derID
zurückgegebenen Aufgabe bestimmen, um ihren Status zu bestimmen.Sie können auch einen
SOAP over HTTP
Webdienst einmischen, um Ihrer Architektur RPC hinzuzufügen.Abfrage aller Aufgaben für eine bestimmte Ressource
GET http://server/api/myCollection/123/tasks
Beispiel für eine Aufgabenressource
PUT http://server/api/tasks
==> gibt die ID der Aufgabe zurück
223334
GET http://server/api/tasks/223334
REST - Verwenden von POST zum Auslösen von Aktionen
Sie können
POST
einer Ressource jederzeit zusätzliche Daten hinzufügen. Meiner Meinung nach würde dies den Geist von REST verletzen, aber es wäre immer noch konform.Sie können einen ähnlichen POST durchführen:
POST http://server/api/collection/123
{ "action" : "send-email" }
Sie aktualisieren die Ressource 123 aus der Sammlung mit zusätzlichen Daten. Diese zusätzlichen Daten sind im Wesentlichen eine Aktion, die das Backend anweist, eine E-Mail für diese Ressource zu senden.
Das Problem, das ich dabei habe, ist, dass ein
GET
auf der Ressource diese aktualisierten Daten zurückgibt. Dies würde jedoch Ihre Anforderungen lösen und dennoch RESTful sein.SOAP - Webdienst, der von REST erhaltene Ressourcen akzeptiert
Erstellen Sie einen neuen WebService, in dem Sie E-Mails basierend auf der vorherigen Ressourcen-ID über die REST-API senden können. Ich werde hier nicht näher auf SOAP eingehen, da es sich bei der ursprünglichen Frage um REST handelt und diese beiden Konzepte / Technologien nicht verglichen werden sollten, da es sich um Äpfel und Orangen handelt .
Frage 2
Sie haben hier auch einige Optionen:
Es scheint, dass viele größere Unternehmen, die REST-APIs veröffentlichen, eine
search
Sammlung verfügbar machen , die eigentlich nur eine Möglichkeit ist, Abfrageparameter zu übergeben, um Ressourcen zurückzugeben.GET http://server/api/search?q="type = myCollection & someField >= someval"
Was eine Sammlung voll qualifizierter REST-Ressourcen zurückgeben würde, wie z.
Oder Sie können MVEL als Abfrageparameter zulassen .
Frage 3
Ich bevorzuge die Unterebenen, als die andere Ressource mit einem Abfrageparameter abfragen zu müssen. Ich glaube nicht, dass es auf die eine oder andere Weise eine Regel gibt. Sie können beide Möglichkeiten implementieren und dem Anrufer ermöglichen, anhand der ersten Eingabe in das System zu entscheiden, welche Option besser geeignet ist.
Anmerkungen
Ich bin nicht einverstanden mit den Lesbarkeitskommentaren anderer. Ungeachtet dessen, was manche vielleicht denken, ist REST immer noch nicht für den menschlichen Verzehr bestimmt. Es ist für den Maschinenverbrauch. Wenn ich meine Tweets sehen möchte, benutze ich die reguläre Twitters-Website. Ich führe kein REST GET mit ihrer API durch. Wenn ich programmgesteuert etwas mit meinen Tweets machen möchte, verwende ich deren REST-API. Ja, APIs sollten verständlich sein, aber Ihre
gte
sind nicht so schlecht, sie sind einfach nicht intuitiv.Die andere Hauptsache bei REST ist, dass Sie an jedem beliebigen Punkt in Ihrer API beginnen und zu allen anderen zugeordneten Ressourcen navigieren können sollten, ohne die genaue URL der anderen Ressourcen im Voraus zu kennen. Die Ergebnisse des
GET
VERB in REST sollten die vollständige REST-URL der Ressourcen zurückgeben, auf die verwiesen wird. Anstelle einer Abfrage, die die ID einesPerson
Objekts zurückgibt, wird die vollständig qualifizierte URL zurückgegeben, zhttp://server/api/people/13
. Dann können Sie jederzeit programmgesteuert durch die Ergebnisse navigieren, auch wenn sich die URL geändert hat.Antwort auf Kommentar
In der realen Welt müssen tatsächlich Dinge geschehen, die keine Ressource erstellen, lesen, aktualisieren oder löschen (CRUD).
Zusätzliche Maßnahmen können für Ressourcen ergriffen werden. Typische relationale Datenbanken unterstützen das Konzept der gespeicherten Prozeduren. Dies sind zusätzliche Befehle, die für einen Datensatz ausgeführt werden können. REST hat dieses Konzept nicht von Natur aus. Und es gibt keinen Grund dafür. Diese Arten von Aktionen eignen sich perfekt für RPC- oder SOAP-Webdienste.
Dies ist das allgemeine Problem, das ich bei REST-APIs sehe. Entwickler mögen die konzeptionellen Einschränkungen, die REST umgeben, nicht, deshalb passen sie es an, um zu tun, was sie wollen. Das macht es jedoch zu einem RESTful-Service. Im Wesentlichen werden diese URLs zu
GET
Aufrufen von Pseudo-REST-ähnlichen Servlets.Sie haben einige Möglichkeiten:
POST
zusätzlicher Daten zur Ressource, um eine Aktion auszuführen.Wenn Sie einen Abfrageparameter verwenden würden, welches HTTP-VERB würden Sie zum erneuten Senden der E-Mail verwenden?
GET
- Sendet dies die E-Mail erneut UND gibt die Daten der Ressource zurück? Was ist, wenn ein System diese URL zwischengespeichert und wie die eindeutige URL für diese Ressource behandelt hat? Jedes Mal, wenn sie auf die URL klicken, wird eine E-Mail erneut gesendet.POST
- Sie haben keine neuen Daten an die Ressource gesendet, sondern nur einen zusätzlichen Abfrageparameter.Basierend auf allen gegebenen Anforderungen wird das Problem gelöst, wenn Sie eine
POST
Ressource mitaction field
POST-Daten bearbeiten.quelle
Frage 1: Ist es angemessen, eine Art Aktionsaufruf mit einer Ressourcen-URI zu mischen [oder], wäre es besser, die Aktion anzugeben und die Ressourcen-ID an sie zu übergeben?
Gute Frage. In diesem Fall würde ich Ihnen raten, den letzteren Ansatz zu verwenden, nämlich die Aktion anzugeben und eine Ressourcen-ID an sie zu übergeben. Auf diese Weise ruft Ihre Ressource beim ersten
/sendEmail
Ändern die Aktion (Randnotiz: Sie müssen sie nicht als "erneutes Senden" bezeichnen) als separate RESTful-Anforderung auf (die Sie später unabhängig von der zu ändernden Ressource später immer wieder aufrufen können ).Frage 2: Bezüglich der Verwendung eines Vergleichsoperators wie folgt:
/collection?someField:gte=someval
Dies ist zwar technisch in Ordnung, aber wahrscheinlich eine schlechte Idee. Eines der wichtigsten Prinzipien von REST ist die Lesbarkeit. Ich würde vorschlagen, dass Sie einfach den Vergleichsoperator als einen anderen Parameter übergeben, zum Beispiel:
/collection?someField=someval&operator=gte
und natürlich Ihre API so gestalten, dass sie einen Standardfalloperator
berücksichtigt (für den Fall, dass der Parameter nicht in der URI enthalten ist).Frage 3: Gibt es jemals einen guten Grund dafür, dass ein REST-URI mehr als zwei Ebenen tief ist?
Jep; zur Abstraktion. Ich habe einige REST-APIs gesehen, die Abstraktionsebenen über mehrere URI-Ebenen verwenden, zum Beispiel:
/vehicles/cars/123
oder/vehicles/bikes/123
die es Ihnen wiederum ermöglichen, mit nützlichen Informationen zu arbeiten, die sich sowohl auf Sammlungen/vehicles
als auch auf/vehicles/bikes
Sammlungen beziehen . Trotzdem bin ich kein großer Fan dieses Ansatzes. In der Praxis müssen Sie dies selten tun, und es besteht die Möglichkeit, dass Sie die API so umgestalten, dass nur zwei Ebenen verwendet werden.Und ja, wie aus den obigen Kommentaren hervorgeht, ist es in Zukunft am besten, Ihre Fragen in separate Beiträge aufzuteilen;)
quelle
/collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq
.Bei Frage 2 kann eine andere Alternative flexibler sein: Betrachten Sie jede Suche als eine Ressource, die der Benutzer vor der Verwendung erstellt.
Nehmen wir an, Sie haben einen "Such" -Container, dort führen Sie einen
POST /api/searches/
mit der Abfragespezifikation zum Inhalt durch. Es kann sich um ein JSON-, XML- oder sogar ein SQL-Dokument handeln, was auch immer für Sie einfacher ist. Wenn die Abfrage korrekt analysiert wird, wird beispielsweise eine neue Suche als neue Ressource mit einem eigenen URI erstellt/api/searches/q123/
Dann kann der Client einfach
GET /api/searches/q123/
die Abfrageergebnisse abrufen.Schließlich können Sie den Client entweder auffordern, die Abfrage zu löschen, oder sie nach dem Schließen der Sitzung löschen.
quelle
Nicht, es ist nicht angemessen, da IRIs zum Identifizieren von Ressourcen und nicht von Operationen dienen (jedoch verwenden ppl diesen Methodenüberschreibungsansatz für eine Weile, wenn die Verwendung von Nicht-POST- und GET-Methoden nicht unterstützt wird). Sie können nach einer geeigneten HTTP-Methode suchen oder eine neue erstellen. POST kann in diesen Fällen Ihr Freund sein (ppl verwenden es, wenn sie keine geeignete Methode finden und die Anfrage nicht abgerufen wird). Ein weiterer Ansatz, um Ressourcen aus dem E-Mail-Versand
POST /emails
zu erstellen und so die E- Mails zu senden, ohne eine echte Ressource zu erstellen. Übrigens. Die URI-Struktur enthält keine Semantik. Aus REST-Sicht spielt es also keine Rolle, welche Art von URIs Sie verwenden. Was zählt, sind die Metadaten (z. B. Linkbeziehung ), die den Links zugewiesen sind, die Sie an die Kunden gesendet haben.Sie müssen keine eigene Abfragesprache erstellen. Ich würde lieber eine bereits vorhandene verwenden und den Link-Metadaten eine Abfragebeschreibung hinzufügen. Sie sollten dazu wahrscheinlich einen RDF-Medientyp (z. B. JSON-LD) verwenden oder einen benutzerdefinierten MIME-Typ verwenden (afaik gibt es kein Nicht-RDF-Format, das dies unterstützt). Durch die Verwendung vorhandener Standards wird Ihr Client vom Server entkoppelt. Darum geht es bei der einheitlichen Schnittstellenbeschränkung.
Wie bereits erwähnt, spielt die URI-Struktur aus REST-Sicht keine Rolle. Sie könnten
/x71fd823df2
zum Beispiel verwenden. Für die Clients wäre dies immer noch sinnvoll, da sie die den Links zugewiesenen Metadaten und nicht die URI-Struktur überprüfen. Der Hauptzweck von URI ist die Identifizierung von Ressourcen. Im URI-Standard geben sie an, dass der Pfad hierarchische Daten enthält und die Abfrage nicht hierarchische Daten enthält. Aber es kann sehr subjektiv sein, was hierarchisch ist. Aus diesem Grund treffen Sie auch auf mehrere Ebenen tiefe URIs und URIs mit langen Abfragen.Sie sollten mindestens die REST-Einschränkungen aus der Fielding-Dissertation , dem HTTP-Standard und wahrscheinlich Web-APIs der 3. Generation von Markus lesen .
quelle