Verschiedene Konzepte im Zusammenhang mit REST-Konflikten treten in meinem Kopf auf, wenn ich versuche, sie zu implementieren.
Ich habe ein REST-volles Back-End-API-System, das die Geschäftslogik enthält, und eine Webanwendung, die die Benutzeroberfläche bereitstellt. Aus verschiedenen Quellen zu REST (insbesondere REST in der Praxis: Hypermedien und Systemarchitektur ) weiß ich, dass ich keine unformatierten Bezeichner meiner Entitäten offen legen, sondern Hyperlinks mit zurückgeben sollte rel="self"
.
Betrachten Sie das Beispiel. Die REST-API verfügt über eine Ressource, die eine Person zurückgibt:
<Person>
<Links>
<Link rel="self" href="http://my.rest.api/api/person/1234"/>
</Links>
<Pets>
<Link rel="pet" href="http://my.rest.api/api/pet/678"/>
</Pets>
</Person>
Das Problem tritt bei der Webanwendung auf. Angenommen, es wird eine Seite zurückgegeben, die einen Hyperlink zu Browsern enthält:
<body class="person">
<p>
<a href="http://my.web.app/pet/???????" />
</p>
</body>
Was soll ich in das href
Attribut setzen? Wie behalte ich die URL der API-Entität in der Webanwendung, um die Entität abzurufen, wenn ein Benutzer die Zielseite öffnet?
Die Anforderungen scheinen sich zu widersprechen:
- Der Hyperlink
href
sollte zur Webanwendung führen, da dies das System ist, auf dem die Benutzeroberfläche gehostet wird - Die
href
sollte eine ID der Entität haben, da die Web-App in der Lage sein muss, die Entität zu adressieren, wenn die Zielseite geöffnet wird - Die Web-App sollte keine REST-URLs parsen / konstruieren, da diese nicht REST-voll sind, heißt es in dem genannten Buch
URIs sollten für Verbraucher undurchsichtig sein. Nur der Emittent des URI kann ihn interpretieren und einer Ressource zuordnen.
Ich kann also nicht einfach 1234
die API-Antwort-URL auslesen, da ich sie als RESTful-Client so behandeln sollte, als wäre sie so etwas wie http://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0j
. Andererseits muss ich eine URL angeben, die zu meiner Web-App führt und ausreicht, damit die App die ursprüngliche URL der API wiederherstellt und diese URL für den Zugriff auf die API-Ressourcen verwendet.
Am einfachsten ist es wahrscheinlich, die API-URLs von Ressourcen als Zeichenfolgen-IDs zu verwenden. Aber Webseiten-URLs wie http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234
sind hässlich.
Für eine Desktop-App oder eine Single-Page-Javascript-App scheint alles ganz einfach zu sein. Da sie ununterbrochen leben, können sie die URLs nur zusammen mit den Serviceobjekten für die Anwendungslebensdauer im Speicher behalten und sie bei Bedarf verwenden.
Mit einer Web-App kann ich mir mehrere Ansätze vorstellen, aber alle scheinen komisch:
- Ersetzen Sie den Host in den API-URLs und behalten Sie nur das Ergebnis bei. Der große Nachteil ist, dass die Webanwendung die URL verarbeiten muss, die von der API generiert wird, was eine monströse Kopplung bedeutet. Darüber hinaus ist es nicht wieder RESTful, weil meine Web-App beginnt, die URLs zu interpretieren.
- Stellen Sie die Roh-IDs in der REST-API zusammen mit den Links bereit, erstellen Sie daraus die URLs der Webanwendung und ermitteln Sie anhand der IDs auf dem Webanwendungsserver die erforderlichen Ressourcen in der API. Dies ist besser, wirkt sich jedoch auf die Leistung des Webanwendungsservers aus, da die Webanwendung die REST-Dienstnavigation durchlaufen muss, um eine Kette von Get-by-ID-Anforderungen in irgendeiner Form auszugeben, um alle Anforderungen eines Browsers zu verarbeiten. Für eine etwas verschachtelte Ressource kann dies kostspielig sein.
- Speichern Sie alle
self
vom API zurückgegebenen URLs in einer permanenten Zuordnung (DB?) Auf dem Webanwendungsserver. Generieren Sie einige IDs für diese, erstellen Sie mithilfe der IDs die URLs der Webanwendungsseite und rufen Sie die URLs der REST-Serviceressourcen ab. Dh ich behalte diehttp://my.rest.api/pet/678
URL irgendwo mit einem neuen Schlüssel, z. B.3
und generiere die Webseiten-URL alshttp://my.web.app/pet/3
. Dies sieht aus wie eine Art HTTP-Cache-Implementierung. Ich weiß nicht warum, aber es kommt mir komisch vor.
Oder bedeutet dies alles, dass RESTful-APIs nicht als Back-End für Webanwendungen dienen können?
quelle
Antworten:
Bearbeitet, um Fragenaktualisierungen zu adressieren, vorherige Antwort entfernt
Wenn ich mir Ihre Änderungen an Ihrer Frage ansehe, denke ich, dass ich das Problem, mit dem Sie konfrontiert sind, besser verstehe. Da es in Ihren Ressourcen kein Feld gibt, das eine Kennung darstellt (nur ein Link), können Sie in Ihrer GUI nicht auf diese bestimmte Ressource verweisen (dh auf einen Link zu einer Seite, die ein bestimmtes Haustier beschreibt).
Als Erstes muss festgestellt werden, ob ein Haustier ohne Besitzer überhaupt einen Sinn ergibt. Wenn wir ein Haustier ohne Besitzer haben können, dann würde ich sagen, wir brauchen eine Art einzigartiges Eigentum an dem Haustier, mit dem wir uns darauf beziehen können. Ich glaube nicht, dass dies die Nichtveröffentlichung der ID verletzen würde, da die tatsächliche Ressourcen-ID immer noch in einem Link versteckt ist, den der REST-Client nicht analysieren würde. In diesem Sinne könnte unsere Haustierressource so aussehen:
Wir können jetzt den Namen dieses Begleiters von Spot auf Fido aktualisieren, ohne in der gesamten Anwendung mit den tatsächlichen Ressourcen-IDs herumspielen zu müssen. Ebenso können wir auf dieses Haustier in unserer GUI mit etwas verweisen, wie:
Wenn das Haustier ohne einen Besitzer keinen Sinn ergibt (oder Haustiere im System ohne einen Besitzer nicht erlaubt sind), können wir den Besitzer als Teil der "Identität" des Haustieres im System verwenden:
Eine kleine Anmerkung, wenn sowohl Haustiere als auch Menschen getrennt voneinander existieren können, würde ich den Einstiegspunkt für die API nicht zur Ressource "Menschen" machen. Stattdessen würde ich eine allgemeinere Ressource erstellen, die einen Link zu Personen und Haustieren enthält. Es könnte eine Ressource zurückgeben, die wie folgt aussieht:
Wenn Sie also nur den ersten Einstiegspunkt in die API kennen und keine der URLs verarbeiten, um die System-IDs herauszufinden, können Sie Folgendes tun:
Der Benutzer meldet sich bei der Anwendung an. Der REST-Client greift auf die gesamte Liste der verfügbaren Personenressourcen zu, die wie folgt aussehen können:
Die GUI durchläuft jede Ressource und druckt ein Listenelement für jede Person aus, wobei der eindeutige Name als "ID" verwendet wird:
Dabei könnte es auch jeden gefundenen Link mit einem rel von "pet" verarbeiten und die pet-Ressource wie folgt abrufen:
Hiermit kann ein Link wie folgt gedruckt werden:
oder
Wenn wir mit dem ersten Link fortfahren und davon ausgehen, dass unsere Eintragsressource einen Link mit einer Beziehung von "Haustieren" enthält, würde der Kontrollfluss in der GUI ungefähr so aussehen:
Die Verwendung des zweiten Links wäre eine ähnliche Ereigniskette, mit der Ausnahme, dass People der Einstiegspunkt in die API ist und wir zuerst eine Liste aller Personen im System erhalten, die übereinstimmende finden und dann alle dazugehörigen Haustiere finden Gehen Sie zu dieser Person (verwenden Sie erneut das rel-Tag) und suchen Sie die Person mit dem Namen Spot, damit wir die entsprechenden Informationen anzeigen können.
quelle
rel
s zum Auswählen der Links verwenden, jedoch keine Kenntnis der URL-Struktur voraussetzen. REST behauptet, dass es einer API freigestellt ist, URLs nach Belieben zu ändern, sofern dieserel
unverändert bleiben. Durch das Parsen von URLs kommen wir SOAP näher als REST.Ich frage mich, ob es sich lohnt, zwischen einer REST-API und einer Webanwendung zu unterscheiden. Ihre „Web - Anwendung“ sollte nur alternative sein (HTML) Darstellungen der gleichen Ressourcen - das heißt, ich verstehe nicht , wie oder warum Sie erwarten , den Zugang
http://my.rest.api/...
undhttp://my.web.app/...
und dass sie sein , gleichzeitig gleich und verschieden.Ihr "Client" ist in diesem Fall der Browser und versteht HTML und JavaScript. Das ist meiner Meinung nach die Webanwendung. Jetzt können Sie anderer Meinung sein und denken, dass Sie über foo.com auf diese Webanwendung zugreifen und alles andere über api.foo.com verfügbar machen - aber Sie müssen sich dann fragen, wie foo.com mir die Darstellung der Ressource zur Verfügung gestellt hat. Das "Back-End" von foo.com ist in der Lage zu verstehen, wie Ressourcen von api.foo.com gefunden werden. Ihre Webanwendung ist lediglich ein Proxy geworden - nicht anders, als wenn Sie alle zusammen mit einer anderen API (von einer anderen Person) gesprochen hätten.
Ihre Frage kann also verallgemeinert werden zu: "Wie kann ich Ressourcen mit meinen eigenen URIs beschreiben, die in anderen Systemen vorhanden sind?" Das ist trivial, wenn man bedenkt, dass nicht der Client (HTML / JavaScript), sondern der Server verstehen muss, wie das geht. Wenn Sie meiner ersten Herausforderung zustimmen, können Sie sich Ihre Webanwendung einfach als separate REST-API vorstellen, die eine andere REST-API überträgt oder an diese delegiert.
Wenn Ihr Client darauf zugreift, muss
my.web.app/pets/1
er die Pet-Schnittstelle präsentieren, da dies entweder von der serverseitigen Vorlage zurückgegeben wurde oder wenn es sich um eine asynchrone Anforderung für eine andere Darstellung (z. B. JSON oder XML) handelt, wird dies im Content-Type-Header angezeigt .Der Server, der dies bereitstellt, ist dafür verantwortlich, zu verstehen, was ein Haustier ist und wie ein Haustier auf dem fernen System erkannt wird. Wie Sie dies tun, bleibt Ihnen überlassen - Sie können einfach die ID nehmen und einen anderen URI generieren, was Sie für unangemessen halten, oder Sie können über eine eigene Datenbank verfügen, in der der Remote-URI gespeichert ist und die Anforderung als Proxy fungiert. Das Speichern dieser URI ist in Ordnung - es entspricht einem Lesezeichen. Sie würden dies alles nur tun, um einen separaten Domainnamen zu haben. Ich weiß ehrlich gesagt nicht, warum Sie das wollen - Ihre REST-API-URIs müssen auch lesezeichenfähig sein.
Sie haben das meiste bereits in Ihrer Frage angesprochen, aber ich glaube, Sie haben es auf eine Weise umrahmt, die wirklich nicht anerkannt hat, dass es die praktische Art ist, das zu tun, was Sie tun möchten (basierend auf dem, was ich für richtig halte) willkürliche Einschränkung - dass die API und die Anwendung getrennt sind). Wenn Sie die Frage stellen, ob REST-APIs kein Back-End für Webanwendungen sein können, und wenn Sie darauf hinweisen, dass die Leistung ein Problem darstellt, konzentrieren Sie sich vermutlich auf die falschen Dinge. Es ist so, als ob Sie sagen, Sie können keinen Mashup erstellen. Es ist, als würde man sagen, dass das Web nicht funktioniert.
quelle
my.web.app/pets/3
einfügen, ohne REST-API-URLs zu analysieren?"my.web.app/pets/3
eingeben, ohne die URL der entsprechenden REST-API-Ressource zu analysierenmy.rest.api/v0/persons/2/pets/3
? Oder was lege ich da hin? '3
in ,app/pets/3
weilapp/pets/3
undurchsichtig ist, es zeigt auf , was auch immer Ressource Ihres Web - App will. Wenn dies eine komponierte Ansicht mehrerer anderer Ressourcen ist (in anderen Systemen - Ihre API ist eine davon), müssen Sie die Hyperlinks zu diesen Systemen auf dem Web-App-Server speichern und sie dann abrufen und in ihre Darstellungen auflösen ( zB JSON oder XML) und dann als Teil Ihrer Antwort bereitstellen.board/1
hindeutet,facebook.com/post/123
undtwitter.com/status/789
- wenn Sie eine Darstellung Ihres Boards bereitstellen, müssten Sie diese URIs in eine Darstellung auflösen, mit der Sie arbeiten können. Cache wo nötig.Vorwort
Diese Antwort befasst sich speziell mit der Frage, wie Sie Ihr eigenes URL-Schema verwalten können, einschließlich eindeutiger lesezeichenfähiger URLs für Ressourcen, für die die Back-End-REST-API keinen Bezeichner explizit verfügbar macht, und ohne die von der API bereitgestellten URLs zu interpretieren.
Die Auffindbarkeit erfordert ein gewisses Maß an Wissen. Hier ist meine Sicht auf ein reales Szenario:
Angenommen, wir möchten eine Suchseite, auf der
http://my.web.app/person
die Ergebnisse für jede Person einen Link zur Detailseite enthalten. Das einzige , was unser Front-End - Code muss wissen , um überhaupt etwas zu tun , ist die Basis - URL für seine REST - Datenquelle:http://my.rest.api/api
. Die Antwort auf eine GET-Anforderung an diese URL lautet möglicherweise:Da wir beabsichtigen, eine Liste von Personen anzuzeigen, senden wir als nächstes eine
GET
Anfrage über denperson
Link href an die href, die möglicherweise zurückgibt:Wir möchten Suchergebnisse anzeigen, daher verwenden wir den Suchdienst, indem wir eine
GET
Anfrage an densearch
Link href senden , die möglicherweise Folgendes zurückgibt:Wir haben endlich unsere Ergebnisse, aber wie erstellen wir unsere Front-End-URLs?
Entfernen wir den Teil, den wir mit Sicherheit kennen: die API-Basis-URL, und verwenden Sie den Rest als Front-End-Kennung:
http://my.rest.api/api
http://my.rest.api/api/person/1
/person/1
http://my.web.app
http://my.web.app/person/1
Unsere Ergebnisse könnten so aussehen:
Wenn ein Benutzer diesem Front-End-Link zur Detailseite folgt, an welche URL senden wir die
GET
Anfrage nach Details zu diesem bestimmten Linkperson
? Wir kennen unsere Methode zum Zuordnen von Back-End-URLs zu Front-End-URLs und kehren sie einfach um:http://my.web.app/person/1
http://my.web.app
/person/1
http://my.rest.api/api
http://my.rest.api/api/person/1
Wenn sich die REST-API dahingehend ändert, dass sich eine
person
URL jetzt befindethttp://my.rest.api/api/different-person-base/person/1
und bereits ein Lesezeichen vorhanden isthttp://my.web.app/person/1
, sollte die REST-API (zumindest zeitweise) Abwärtskompatibilität gewährleisten, indem sie auf die alte URL mit einer Umleitung auf die neue antwortet. Alle generierten Front-End-Links würden die neue Struktur automatisch enthalten.Wie Sie wahrscheinlich bemerkt haben, müssen wir einige Dinge wissen, um in der API zu navigieren:
person
Beziehungsearch
BeziehungIch glaube nicht, dass daran etwas falsch ist. Wir gehen zu keinem Zeitpunkt von einer bestimmten URL-Struktur aus, sodass sich die Struktur der Entity-URL
http://my.rest.api/api/person/1
ändern könnte. Solange die API Abwärtskompatibilität bietet, funktioniert unser Code weiterhin.Sie haben gefragt, wie unsere Routing-Logik den Unterschied zwischen zwei Front-End-URLs erkennen kann:
http://my.rest.api/api/person/1
http://my.rest.api/api/pet/3
.Zuerst möchte ich darauf hinweisen, dass Sie in Ihrem Kommentar die API-Basis verwendet haben, wenn wir in unserem Beispiel separate Basis-URLs für die Benutzeroberfläche und die REST-API verwenden. Ich werde das Beispiel mit separaten Basen fortsetzen, aber das Teilen einer Basis ist kein Problem. Wir können (oder sollten) in der Lage sein, UI-Routing-Methoden mithilfe des Medientyps aus dem Accept-Header der Anforderung zuzuordnen.
Bei der Weiterleitung zu einer bestimmten Detailseite können wir diese beiden URLs nicht unterscheiden, wenn wir streng darauf achten, dass keine Kenntnis über die Struktur der
self
von der API bereitgestellten URL (dh der undurchsichtigen Zeichenfolgen-ID) erlangt wird. Damit dies funktioniert, fügen wir in unsere Front-End-URLs einen weiteren unserer bekannten Informationen ein - den Entitätstyp, mit dem wir arbeiten.Bisher hatten unsere Front-End-URLs das folgende Format:
${UI base}/${opaque string id}
Das neue Format könnte sein:
${UI base}/${entity type}/${opaque string id}
Mit dem
/person/1
Beispiel würden wir also enden mithttp://my.web.app/person/person/1
.Mit diesem Format würde unsere UI-Routing-Logik funktionieren
/person/person/1
, und da wir wissen, dass das erste Token in der Zeichenfolge von uns selbst eingefügt wurde, können wir es herausziehen und darauf basierend zu der entsprechenden Detailseite (in diesem Beispiel Person) weiterleiten. Wenn Sie verlegen über diese URL sind, können wir etwas mehr hinzufügen. vielleicht:http://my.web.app/person/detail/person/1
In diesem Fall würden wir das
/person/detail
for routing analysieren und den Rest als undurchsichtige Zeichenfolgen-ID verwenden.Ich vermute, Sie meinen, da unsere generierte Front-End-URL einen Teil der API-URL enthält, benötigen wir eine Codeänderung, um die mit einem Lesezeichen versehene URL in die zu übersetzen, wenn sich die API-URL-Struktur ändert, ohne die alte Struktur zu unterstützen neue Version der API-URL. Mit anderen Worten, wenn die REST-API die ID einer Ressource (die undurchsichtige Zeichenfolge) ändert, können wir mit der alten ID nicht mit dem Server über diese Ressource sprechen. Ich denke nicht, dass wir eine Codeänderung in dieser Situation vermeiden können.
Sie können eine beliebige URL-Struktur verwenden. Letztendlich muss eine lesezeichenfähige URL für eine bestimmte Ressource etwas enthalten, mit dem Sie eine API-URL abrufen können, die diese Ressource eindeutig identifiziert. Wenn Sie Ihren eigenen Bezeichner generieren und ihn mit der API-URL wie in Ansatz 3 zwischenspeichern, funktioniert dies so lange, bis jemand versucht, die mit einem Lesezeichen versehene URL zu verwenden, nachdem dieser Eintrag aus dem Cache gelöscht wurde.
Die Antwort hängt von der Beziehung ab. In beiden Fällen benötigen Sie eine Möglichkeit, das Front-End API-URLs zuzuordnen.
quelle
person/1
,pet/3
) behandelt, wie würde sie dann wissen, dass beim Öffnen eines Browsers diehttp://my.rest.api/api/person/1
Benutzeroberfläche der Person angezeigt wird , und ob dies der Fall ist öffnethttp://my.rest.api/api/pet/3
sich dann pet UI?Seien wir ehrlich, es gibt keine magische Lösung. Haben Sie Richardson Maturity Model gelesen ? Es unterteilt die Reife der REST-Architektur in drei Ebenen: Ressourcen, HTTP-Verben und Hypermedia-Steuerelemente.
Dies sind Hypermedia-Steuerelemente. Brauchen Sie es wirklich? Dieser Ansatz hat einige sehr gute Vorteile (Sie können darüber hier lesen ). Es gibt jedoch keine kostenlosen Mahlzeiten und Sie müssen hart arbeiten (z. B. Ihre zweite Lösung), wenn Sie diese erhalten möchten.
Es ist eine Frage des Gleichgewichts - möchten Sie Leistung opfern (und Ihren Code komplizierter machen), aber ein flexibleres System erhalten? Oder ziehen Sie es vor, die Dinge schneller und einfacher zu halten, aber später zu zahlen, wenn Sie Änderungen an Ihrer API / Ihrem Modell vornehmen?
Als jemand, der ein ähnliches System entwickelt hat (Business Logic Tier, Web Tier und Web Clients), habe ich die zweite Option gewählt. Da meine Gruppe alle Ebenen entwickelt hat, haben wir beschlossen, dass es besser ist, ein wenig zu koppeln (indem wir die Webebene über Entitäts-IDs und das Erstellen von API-URLs informieren) und im Gegenzug Code zu erhalten, der einfacher ist. Die Abwärtskompatibilität war in unserem Fall ebenfalls nicht relevant.
Wenn die Webanwendung von einem Drittanbieter entwickelt wurde oder wenn Abwärtskompatibilität ein Problem war, haben wir möglicherweise eine andere Wahl getroffen, da es von großem Wert war, die URL-Struktur ändern zu können, ohne die Webanwendung zu ändern. Grund genug, den Code zu komplizieren.
Ich denke, das bedeutet, dass Sie keine perfekte REST-Implementierung erstellen müssen. Sie können mit Ihrer zweiten Lösung fortfahren , Entity- IDs offenlegen oder API-URLs übergeben . Es ist in Ordnung, solange Sie die Implikationen und Kompromisse verstehen.
quelle
Ich denke, wenn du an etwas ähnlichem festhältst,
Atom Syndication Format
bist du gut.Hier können die Metadaten, die den dargestellten Eintrag beschreiben, mit zusätzlichen Elementen / Attributen angegeben werden:
Enthält gemäß [RFC4287] eine URI, die den Eintrag eindeutig identifiziert
Wie pro [RFC4287] , ist dieses Element optional. Wenn enthalten, enthält es den URI, den ein Client zum Abrufen des Eintrags verwenden soll.
Das sind nur meine zwei Cent.
quelle
Sorgen Sie sich nicht um die URLs, sondern um die Medientypen.
Siehe hier (insbesondere dritter Punkt).
Bei einer typischen Web-App ist der Client ein Mensch . Der Browser ist nur ein Agent .
So ein Anchor-Tag gefällt
entspricht sowas
Die URL ist für die Benutzerin immer noch undurchsichtig. Sie kümmert sich nur um die Medientypen (z
text/html, application/pdf, application/flv, video/x-flv, image/jpeg, image/funny-cat-picture etc
. B. ). Der im Anker (und im title-Attribut) enthaltene beschreibende Text ist nur eine Möglichkeit, den Beziehungstyp so zu erweitern, dass er für den Menschen verständlich ist.Der Grund dafür, dass der URI für Clients undurchsichtig sein soll, besteht darin, dass Sie die Kopplung reduzieren (eines der Hauptziele von REST). Der Server kann die URIs ändern / reorganisieren, ohne dass dies Auswirkungen auf den Client hat (sofern Sie eine gute Caching-Richtlinie haben - was möglicherweise überhaupt kein Caching bedeutet).
Zusammenfassend
Stellen Sie nur sicher, dass sich der Client (Mensch oder Maschine) um die Medientypen und die Beziehungen kümmert und nicht um die URLs.
quelle
Ich denke du hast recht, das ist der einfachste Weg. Sie können die URLs relativieren
http://my.rest.api/api
, um sie weniger hässlich zu machen:Wenn die von der API bereitgestellte URL nicht relativ zu dieser Basis ist, verschlechtert sie sich in die hässliche Form:
Um noch einen Schritt weiter zu gehen, überprüfen Sie die Antwort vom API-Server, um festzustellen, welche Art von Ansicht Sie darstellen möchten, und beenden Sie die Codierung des Pfadsegmenttrennzeichens und der Doppelpunkte:
quelle