Ich werde diese Frage auf diese Weise stellen - was sind die Bedenken der Softwareentwicklung, wenn meine REST-API nicht "richtig" implementiert wird?
Was meinst du mit dem "richtigen" Weg? Lassen Sie mich meine Wahrnehmung des richtigen Weges erklären, dann erkläre ich Ihnen, wie ich es mache (nehmen Sie auch an, ich spreche von einer JSON-REST-API).
Der richtige Weg
Staatenlosigkeit. Dies ist der Teil, den ich bekomme. Der Client behält den Status immer zu 100% für immer bei. Es ist nicht die Aufgabe des Servers, sondern die des Kunden.
Die erwarteten Aktionen und Antworten für jedes Verb:
- GET - Ruft eine vollständig angegebene Ressource ab, die nur durch die Berechtigung in der Anforderung oder einen Abfrageparameter begrenzt ist. Dies stellt sicher, dass keine Ressource im Prozess geändert wird.
- POST - Bei einer vollständigen Ressourcenbeschreibung (wie einem JSON-Objekt) wird eine Ressource erstellt und diese Ressource zurückgegeben, wobei auch alle serverseitigen Eigenschaften wie Datumsangaben oder IDs erstellt werden.
- LÖSCHEN - Löscht eine angegebene Ressource und gibt als Antwort nur 200 OK an
- PUT - Bei einer vollständigen Objektdeklaration als Eingabe wird die Ressource an einem bestimmten Speicherort aktualisiert, wobei alle Felder der Ressource auf jedes der in der Eingabe angegebenen Felder aktualisiert werden. Damit wird klargestellt, dass das gesamte Objekt als Eingabe übergeben wird. Die gesamte aktualisierte Ressource wird mit allen Feldern zurückgegeben (entsprechend der Autorisierung oder anderen Eingabeflags).
- PATCH - Wenn nur die Felder für eine Ressource geändert werden sollen, werden nur die Felder in einer angegebenen Ressource aktualisiert, die als Eingabe angegeben werden. (Hier bin ich unklar): Die gesamte Ressource wird zurückgegeben? (Oder sind es nur die aktualisierten Felder? Keine Ahnung. Ist mir egal.)
- Ressourcenpfade. In Anbetracht der Beziehung der Ressourcen zueinander kann ein Ressourcenpfad wie folgt aussehen:
- / parentresource /: id
- / parentresource /: id / childresource
- / parentresource /: id / childresource /: childId
- / parentresource /: id / childresource /: childId / subresource /: subresourceId (In diesem Beispiel gehört eine Unterressource zu einer untergeordneten Ressource, die zu einer übergeordneten Ressource gehört).
So wie ich es machen will
Das Obige ist mein Verständnis davon, wie eine REST-API funktionieren soll. Lassen Sie mich nun einige meiner Variationen zu den oben genannten auflisten:
- PUT / PATCH - Wozu wird die gesamte Ressource zur Änderung übergeben? Ich verwende PUTs nur zum Ändern von Ressourcen und übergebe nur die Felder, die aktualisiert werden sollen. Daher muss ich PATCH nicht verwenden
Ressourcenpfade - Ich verwende GUIDs in meiner Anwendung. Infolgedessen werden sie weltweit einzigartig sein. Warum benötige ich den vollständigen Ressourcenpfad, einschließlich der übergeordneten Ressourcen, wenn ich nur eindeutig auf eine Unterressource selbst verweisen kann? Wie:
/ subresource /: subresourceId
Wenn ich es "richtig" machen würde, würde der Versuch, auf die Subressource zu verweisen, einen vollständigen Pfad erfordern wie:
/ parentresource /: id / childresource /: childId / subresource /: subresourceId
Ist alles notwendig ? Weil ich jetzt eine zusätzliche Fehlerbehandlung haben muss, wenn mein Pfad eine: subresourceId enthält, die nicht tatsächlich einer bestimmten: childId gehört, und ebenso eine: childId, die keiner übergeordneten: id gehört. Meine Serverseite kümmert sich um die Ressourcenautorisierung. Kann ich nicht einfach auf die Ressource selbst verweisen und nicht auf den vollständigen Pfad?Die Rückantwort. Nehmen wir zum Beispiel an, meine Datenstruktur ist ein hierarchischer Baum ohne praktische Grenzen für die Baumtiefe. Ressourcen liegen hierarchisch auf verschiedenen Ebenen im Baum.
- Das GET ist offensichtlich. Wenn ich diesen gesamten Baum erhalte, erwarte ich den gesamten Baum als Antwort mit Ressourcen, die in Ressourcen enthalten sind.
- Wenn ich POST, um eine neue Ressource zu erstellen, PUT zu aktualisieren oder DELETE, um sie zu entfernen, möchte ich die Deltas im Baum sehen, anstatt nur die Ressource zu sehen, die ich erstellt / aktualisiert / gelöscht habe. Ich möchte nicht nach jedem POST, PUT oder DELETE erneut das GET des übergeordneten Baums aufrufen müssen, insbesondere wenn es kleine Änderungen am Baum gibt und ich nur die Deltas sehen möchte.
Hoffentlich sind meine Fragen klar.
Wenn Sie eine REST-Implementierung sehen würden, wie ich sie beschrieben habe, würden Sie sie betrachten und mir Ihre Bedenken hinsichtlich der Softwareentwicklung mitteilen? Wenn ja, was wären sie?
Antworten:
Die allgemeine Antwort lautet, dass Ihre Ideen möglicherweise auf technischer Ebene funktionieren, dies bedeutet jedoch nicht, dass sie den standardisierten Konventionen von REST entsprechen.
Ihre Idee funktioniert auf technischer Ebene, aber es ist einfach nicht so, wie REST beschrieben wurde. Beachten Sie, dass jede Diskussion über Arbeitscode (dh keine Kompilierungs- oder Laufzeitfehler) immer eine Frage der Konvention sein wird , nicht unbedingt der klaren technischen Überlegenheit.
Es gibt viele Nuancen, wie wir "untergeordnete / übergeordnete" Entitäten definieren. Am häufigsten bezieht es sich auf eine Eins-zu-Viele-Beziehung (Eltern-zu-Kinder).
Ich vermute jedoch, dass für REST ein Teil dessen, was ein Kind zu einem Kind macht, darin besteht, dass erwartet wird, dass es nur über die Eltern darauf zugreifen kann und dass es keine eigene global eindeutige (und extern bekannte) Kennung trägt.
Ich vermute, dass dies der gleichen Philosophie folgt (aber nicht unbedingt aus dem gleichen Grund) wie die der Aggregate (und ihrer Wurzeln) in der domänengetriebenen Entwicklung .
In Ihrem Fall fungiert das, was Sie als "übergeordnetes Element" bezeichnen, als aggregierte Wurzel. Die zentrale Anlaufstelle (wenn Sie so wollen) für externe Anrufer.
Vielleicht möchten Sie daraus schließen, dass Ihr Kind tatsächlich ein anderes Aggregat ist. Das mag der Fall sein, aber ich möchte mit dieser Entscheidung eine Warnung herausgeben. Sie sollten Ihre Architektur nicht auf den bestimmten Feldtyp stützen. Sie können nicht wissen, ob Sie weiterhin global eindeutige IDs für alle Ihre Entitäten verwenden. Sollte sich dies aus irgendeinem Grund jemals ändern, werden Sie die Lebensfähigkeit Ihrer REST-Architektur beeinträchtigen. Dies kann dazu führen, dass das Kind nicht mehr eindeutig identifizierbar ist und daher über seine Eltern referenziert werden muss.
Sie verletzen die Reihenfolge der Operationen des Entwurfs. Eine REST-API soll speziell verbraucherunabhängig sein. Die API sollte nicht gemäß den Spezifikationen eines ihrer Verbraucher erstellt werden.
Wenn Sie sagen "Ich möchte die Deltas im Baum sehen", sagen Sie wirklich: "Die konsumierende Anwendung muss nur die Deltas im Baum sehen". Für die REST-API ist das aber nicht ganz wichtig. Es bietet lediglich einen standardisierten Ansatz.
Es liegt in der Natur standardisierter Ansätze, häufig hochgradig anpassbare Tools zu fehlen und stattdessen die am häufigsten verwendeten Tools zu bevorzugen .
Kannst du vom Weg abweichen? Nun, es wird auf technischer Ebene funktionieren. Aber es wird kein reiner REST mehr sein. Dies ist sehr kontextabhängig und Sie müssen die Optionen abwägen.
quelle
Meiner Meinung nach ist das Wichtigste die Möglichkeit, Arbeiten an generische Komponenten zu delegieren, die nur die Standards kennen, nicht Ihren spezifischen Geschäftsfall.
Wenn Sie sich an die einheitliche Benutzeroberfläche halten, ist es für andere Parteien einfacher, Komponenten zu erstellen, die sich gut in Ihre integrieren lassen.
Hier schreibt Fielding 2008
Wir haben es unter anderem geschafft, "langlebig" zu sein, indem wir einen klaren Standard haben, der die Semantik der Nachrichten beschreibt, die wir weitergeben. Wenn sich alle einig sind, was
PUT
bedeutet, können Verbraucher und Hersteller dieser Anfragen unabhängig voneinander entwickelt werden, und Zwischenkomponenten zwischen beiden können sinnvolle Maßnahmen ergreifen, ohne die Details der Nachricht in Ihrem spezifischen Kontext kennen zu müssen.Was bringt es
PUT
dann, wenn man es benutzt ?Dies ist eine absolut gültige Anforderungszeile für eine HTTP-Nachricht, und die Änderung der Semantik wird niemanden verwirren.
Gleichwertig
Welches hat so ziemlich uneingeschränkte Semantik; Der Server kann die Verarbeitung dieser Anforderung nach Belieben implementieren.
Ihre Zurückhaltung bei der Verwendung von PATCH ist in diesem Fall besonders merkwürdig, da bereits Standards für JSON Patch und JSON Merge Patch vorgeschlagen wurden. Die Standardisierung eines Patch-Dokumentformats ist möglicherweise bereits für Sie erledigt.
Eine andere gültige Alternative wäre, das Patch-Dokument als separate Ressource zu behandeln. Semantisch könnte man sich so etwas vorstellen
Dadurch erhalten Sie eine ehrliche, einheitliche Nachrichtensemantik, die die standardisierte Cache-Ungültigmachung opfert .
In einer Codeüberprüfungseinstellung würde ich eine vorgeschlagene Änderung ablehnen, mit der versucht wurde, die Semantik von PUT neu zu definieren.
Die gleiche Überlegung gilt auch für
PUT
; Wenn Ihre Implementierung von PUT von der standardisierten Semantik abweicht, ist Ihre Implementierung für den daraus resultierenden Schaden verantwortlich.Das ist vollkommen in Ordnung. REST ist es egal, welche Schreibweisen Sie für Ihre Ressourcenkennungen verwenden.
Betrachten Sie die Google-Landingpage. Müssen Sie die Schreibweise des URI für das heutige Doodle beachten? oder wo das Suchformular eingereicht wird? Nein natürlich nicht. Die HTML-Nutzdaten enthalten URIs, und die Clients verwenden nur die bereitgestellten Bezeichner auf standardmäßige Weise, ohne diese Bezeichner analysieren zu müssen.
In einen URI codierte Informationen liegen im Ermessen des Ursprungsservers für seine eigenen Zwecke.
Ich würde davon abraten, einen solchen URI als Einstiegspunkt für Ihre API zu verwenden.
https://www.example.org/df8f5f87-15ff-4212-8fb8-4fbca2c7efcf
ist ein bisschen umständlich für den menschlichen Verzehr. Ein von Menschen lesbarer URI, der zur UUID-Ressource umleitet, ist in Ordnung. Ein von Menschen lesbarer URI, der den Inhalt der UUID-Ressource zurückgibt, ist besser.Das ist in Ordnung - schauen Sie sich noch einmal den Standard an .
In einigen Fällen ist es sinnvoll, die neue Darstellung der Ressource als Teil der Antwort zu senden (um dem Client die Latenz einer GET-Anforderung / Antwort zu ersparen).
quelle
What's the point in using PUT then?
-- Tatsächlich. Ein POST würde einwandfrei funktionieren.PATCH
? Die Definition passt besser.REST ist ein Architekturstil zum zustandslosen Manipulieren von Ressourcen (wobei zustandslos bedeutet, dass jede Manipulation für sich steht und nicht von anderen Manipulationen abhängt, die möglicherweise stattgefunden haben).
Die Verwendung der Verben PUT / PATCH / POST / GET / DELETE beruht auf der allgemeinen Verwendung des HTTP-Protokolls, das zum Übertragen und Bearbeiten der Ressourcen verwendet wird. Die Bedeutung dieser Verben wird durch einen Internetstandard ( RFC7231 ) definiert.
Vor diesem Hintergrund ist Ihre Verwendung von PUT nicht Standard und kann andere Entwickler verwirren, die Ihre API verwenden möchten.
In Bezug auf die Ressourcenpfade kümmert sich kaum jemand um die genaue Schreibweise (auch wenn eine untergeordnete Ressource als untergeordnet aufgeführt ist). Was die Leute interessiert, ist, dass jede Ressource eindeutig identifiziert wird. Das System
/parent/:pid/child/:cid
wird häufig verwendet, wenn die untergeordneten IDs nur innerhalb eines übergeordneten Elements eindeutig sind, um weiterhin einen global eindeutigen Pfad zur Ressource zu haben.quelle
Mir scheint, Sie sollten PATCH hier wirklich als Semantik verwenden.
PUT erklärt den genauen gewünschten Zustand. Dies ist nützlich, wenn sich eine Ressource häufig ändert und die gewünschte Änderung in einem bestimmten Kontext erfolgen muss.
PATCH erklärt ein gewünschtes Delta zu allem, was da ist. Dies ist nützlich, wenn sich die Änderung nicht um den Kontext kümmert oder der relevante Kontext viel kleiner als die gesamte Ressource ist.
Ein Bild ist ein gutes Beispiel, wenn es sinnvoll wäre, das Ganze einfach hochzuladen. Es ist wichtig, dass die gesamte Ressource kommuniziert wird, um einen relevanten Kontext sicherzustellen.
Umgekehrt ist es möglicherweise sinnvoller, die Anzahl der Wiedergaben in einer Musikwiedergabeliste zu aktualisieren, um ein Delta zu sein. Nicht weil es sich um eine kleine Änderung handelt, sondern weil das erneute Senden der gesamten Liste Änderungen am Inhalt der Liste leicht rückgängig machen kann.
Nein, Sie müssen wirklich keine Pfade benutzen - niemals. Angenommen, Sie behalten alle Ihre Dateien auf dem Desktop? Nein? Warum nicht?
Wahrscheinlich etwas damit zu tun, dass es einfacher zu sehen ist, dasselbe Problem hier. Eine GUID sagt Ihnen nicht, was Sie tun, während Sie dies einrichten, debuggen oder ausführen.
Wie fühlt es sich an, dieses System zu unterstützen? oder damit interagieren? Wenn Sie nicht darüber nachgedacht haben, nehmen Sie sich etwas Zeit und überlegen Sie, welche Arbeit Sie auf der ganzen Linie erledigen.
Das Vorhandensein expliziter Pfadinformationen dient zur Validierung der Anforderung. Es hilft auch, Informationen so weit zu differenzieren, dass Support- und nachgeschaltete Systementwickler sich diesen URLs nähern und sie verwenden können.
Möglicherweise möchten Sie eine Tiefenbegrenzung festlegen, damit ein kluges Kind nicht einfach für jede erdenkliche Operation die Wurzel Ihrer Website erhält.
Wenn sich das Aktualisieren einer Ressource auf nicht triviale und vorhersehbare Weise auf das übergeordnete Element auswirkt, haben Sie andere Probleme ... Sie müssen sich wirklich das Statusmodell ansehen und herausfinden, warum die Informationen herumspringen.
Wenn Sie nur eine detaillierte Liste der Deltas zurückgeben möchten, warum nicht? Warum nicht mehrere Ausgabeansichten unterstützen, die durch verschiedene Parameter umgeschaltet werden? Jenkins gibt seine API-Antworten in einer Auswahl von XML oder JSON zurück und ermöglicht es Ihnen, mehrere Filter zum Extrahieren des gewünschten Unterbaums anzugeben.
Verwenden Sie es selbst
Um ehrlich zu sein, treten Sie zurück von dem, was Sie machen, und versuchen Sie, es zu unterstützen, oder erstellen Sie eine andere Anwendung, um es zu verwenden (nicht eine Ihrer bereits vorhandenen Anwendungen). Machen Sie etwas Ähnliches für APIs von Drittanbietern, damit Sie einen kleinen Hintergrundkontext haben.
Immer wenn Sie etwas tun müssen, das die Supportanforderung nicht direkt löst oder für die Clientanwendung direkt erforderlich ist, ist die API nicht ideal, und Sie wissen, warum sie nicht ideal ist, was sogar noch besser ist, weil Sie es können Beheben Sie es oder machen Sie nicht den gleichen Fehler.
Wenn beispielsweise Anfragen an eine bestimmte URL ständig fehlschlagen, wie viel Aufwand müssen Sie investieren, um festzustellen, was fehlschlägt und warum? Welche Schritte haben Sie unternommen, hätten Sie einen dieser Schritte vermeiden können, indem Sie einen besseren URI, eine bessere Protokollierung, eine bessere Überwachung usw. hatten?
Wenn Sie einen neuen Client schreiben, wie oft müssen Sie auf die Dokumentation oder den Quellcode der API verweisen? Was könnten Sie tun, um diesen Bedarf zu reduzieren? Was können Sie tun, um Ihre eigenen Erwartungen nicht mehr zu verletzen? Was können Sie tun, um das Problem mit Clientanwendungen zu vereinfachen, ohne den Server zu einem Albtraum zu machen?
Richtiger Weg
Ehrlich gesagt ist der richtige Weg umständlich. REST ist eine Reihe von Methoden, die unter verschiedenen Umständen für eine Reihe von Problemen funktionieren. Wenn Ihr Problem nicht passt, machen Sie es bitte nicht passend, aber behaupten Sie auch nicht, diese Praktiken anzuwenden.
quelle
Die meisten Funktionen von REST apis sind aus einem bestimmten Grund vorhanden, aber möglicherweise nicht aus einem für Sie relevanten Grund. Die Angabe der gesamten Ressource, wie beispielsweise in PUT, ist relevant, wenn Sie Idempotenz benötigen, andernfalls nicht. (Obwohl ich denke, dass es für Benutzer / Kollegen / was auch immer besser wäre, die Tatsache zu bewerben, dass Ihr Endpunkt nicht idempotent ist, indem Sie stattdessen POST oder PATCH verwenden.)
Für den Weg habe ich noch nie gehört, dass es um Ruhe geht.
/root/345dd4dc-e175-455f-b545-85b1b1ce3e82
ist so viel Teil eines Baumes wie/foo/bar/baz
. Vielleicht etwas weniger benutzerfreundlich, aber nicht weniger ruhig, soweit ich sehen kann.Wenn Sie detailliertere Überlegungen dazu wünschen, warum REST so konzipiert wurde, wie es ist, sollten Sie die Originalarbeit lesen: https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf
Wenn Sie lesen, werden Sie vielleicht feststellen, dass es ganz anders ist, als REST heute in Konversationen dargestellt oder in APIs verwendet wird. Offensichtlich haben viele andere Menschen einen guten oder schlechten Grund gefunden, davon abzuweichen.
Mir gefällt besonders dieses Zitat, das Sie vielleicht relevant finden:
quelle
PUT
Anforderungen mehrmals wiederholen , ohne dass Sie es wissen, und sie dürfen dies tun, da die HTTP-Spezifikation angibt, dass diesPUT
idempotent ist . Wenn IhrePUT
s nicht idempotent sind, wird dies Ihren Dienst unterbrechen .GET
undHEAD
rein und frei von Nebenwirkungen ist. Einige Leute haben Daten verloren, weil sie schlecht gestaltete Webanwendungen verwendeten, bei denen das Löschen von Inhalten mit einerGET
Anfrage erfolgte und der Webbeschleuniger glücklichGET
Anfragen an alle URIs sendete , die er finden konnte.