REST Complex / Composite / Nested Resources [geschlossen]

177

Ich versuche, mich mit dem besten Weg zu befassen, um Konzepte in einer REST-basierten API anzusprechen. Flache Ressourcen, die keine anderen Ressourcen enthalten, sind kein Problem. Wo ich in Schwierigkeiten gerate, sind die komplexen Ressourcen.

Zum Beispiel habe ich eine Ressource für ein Comic. ComicBookhat alle Arten von Eigenschaften auf es mögen author, issue number, dateetc.

Ein Comic hat auch eine Liste von 1..nCovers. Diese Abdeckungen sind komplexe Objekte. Sie enthalten viele Informationen über das Cover: den Künstler, ein Datum und sogar ein Basis-64-codiertes Bild des Covers.

Für eine GETauf ComicBookkonnte ich einfach den Comic zurückzukehren, und alle Abdeckungen ihre base64'ed Bilder einschließlich. Das ist wahrscheinlich keine große Sache, um einen einzigen Comic zu bekommen. Angenommen, ich erstelle eine Client-App, die alle Comics im System in einer Tabelle auflisten möchte.
Die Tabelle enthält einige Eigenschaften aus der ComicBookRessource, aber wir werden sicherlich nicht alle Cover in der Tabelle anzeigen wollen. Die Rückgabe von 1000 Comics mit jeweils mehreren Deckblättern würde zu einer lächerlich großen Datenmenge führen, die in diesem Fall für den Endbenutzer nicht erforderlich ist.

Mein Instinkt ist es, Covereine Ressource zu erstellen und ComicBookCover zu enthalten. Also jetzt Coverist eine URI. GETBei Comic-Arbeiten funktioniert jetzt anstelle der riesigen CoverRessource eine URI für jedes Cover zurück, und Kunden können die Cover-Ressourcen nach Bedarf abrufen.

Jetzt habe ich ein Problem beim Erstellen neuer Comics. Sicherlich werde ich mindestens ein Cover erstellen wollen, wenn ich ein Comic, tatsächlich ist das wahrscheinlich eine Geschäftsregel.
Jetzt stecke ich fest. Entweder zwinge ich die Kunden, Geschäftsregeln durchzusetzen, indem ich zuerst einen Covereinsen, den URI für dieses Cover POSTerhalte und dann einen ComicBookmit diesem URI in der Liste einfüge, oder mein POSTOn ComicBooknimmt eine anders aussehende Ressource auf, als sie ausspuckt aus. Die eingehenden Ressourcen für POSTund GETsind tiefe Kopien, wobei die ausgehenden GETs Verweise auf abhängige Ressourcen enthalten.

Die CoverRessource ist wahrscheinlich in jedem Fall erforderlich, da ich als Kunde sicher bin, dass ich in einigen Fällen die Richtung der Deckungen ansprechen möchte. Das Problem besteht also in einer allgemeinen Form, unabhängig von der Größe der abhängigen Ressource. Wie gehen Sie im Allgemeinen mit komplexen Ressourcen um, ohne den Client zu zwingen, nur zu "wissen", wie diese Ressourcen zusammengesetzt sind?

Jgerman
quelle
Ist die Verwendung von RESTFUL SERVICE DISCOVERY sinnvoll?
Treecoder
1
Ich versuche, mich an HATEAOS zu halten, was meiner Meinung nach der Verwendung von so etwas zuwiderläuft, aber ich werde einen Blick darauf werfen.
Jgerman
Unterschiedliche Frage im gleichen Sinne. Das Eigentum unterscheidet sich jedoch von Ihrer vorgeschlagenen Lösung (die in der Frage). stackoverflow.com/questions/20951419/…
Wes

Antworten:

64

@ray, ausgezeichnete Diskussion

@jgerman, vergiss nicht, dass nur weil es REST ist, die Ressourcen von POST nicht in Stein gemeißelt werden müssen.

Was Sie in eine bestimmte Darstellung einer Ressource aufnehmen möchten, liegt bei Ihnen.

Ihr Fall der Cover, auf die separat verwiesen wird, ist lediglich die Erstellung einer übergeordneten Ressource (Comic), auf deren untergeordnete Ressourcen (Cover) verwiesen werden kann. Beispielsweise möchten Sie möglicherweise auch Verweise auf Autoren, Herausgeber, Charaktere oder Kategorien separat bereitstellen. Möglicherweise möchten Sie diese Ressourcen separat oder vor dem Comic erstellen, in dem sie als untergeordnete Ressourcen referenziert werden. Alternativ möchten Sie möglicherweise beim Erstellen der übergeordneten Ressource neue untergeordnete Ressourcen erstellen.

Ihr spezieller Fall der Cover ist insofern etwas komplexer, als für ein Cover wirklich ein Comic erforderlich ist und umgekehrt.

Wenn Sie jedoch eine E-Mail-Nachricht als Ressource und die Absenderadresse als untergeordnete Ressource betrachten, können Sie die Absenderadresse natürlich immer noch separat referenzieren. Holen Sie sich zum Beispiel alles von Adressen. Oder erstellen Sie eine neue Nachricht mit einer vorherigen Absenderadresse. Wenn E-Mail REST war, konnten Sie leicht erkennen, dass viele Ressourcen mit Querverweisen verfügbar sein könnten: / empfangene Nachrichten, / Entwurfsnachrichten, / von Adressen, / an Adressen, / Adressen, / Betreff, / Anhänge, / Ordner , / Tags, / Kategorien, / Labels, et al.

Dieses Tutorial bietet ein hervorragendes Beispiel für Ressourcen, auf die verwiesen wird. http://www.peej.co.uk/articles/restfully-delicious.html

Dies ist das häufigste Muster für automatisch generierte Daten. Beispielsweise veröffentlichen Sie keinen URI, keine ID oder kein Erstellungsdatum für die neue Ressource, da diese vom Server generiert werden. Sie können jedoch den URI, die ID oder das Erstellungsdatum abrufen, wenn Sie die neue Ressource zurückerhalten.

Ein Beispiel für Binärdaten. Beispielsweise möchten Sie Binärdaten als untergeordnete Ressourcen veröffentlichen. Wenn Sie die übergeordnete Ressource erhalten, können Sie diese untergeordneten Ressourcen als dieselben Binärdaten oder als URIs darstellen, die die Binärdaten darstellen.

Formulare und Parameter unterscheiden sich bereits von den HTML-Darstellungen der Ressourcen. Das Posten eines Binär- / Dateiparameters, der zu einer URL führt, ist kein Problem.

Wenn Sie das Formular für eine neue Ressource (/ comic-books / new) oder das Formular zum Bearbeiten einer Ressource (/ comic-books / 0 / edit) erhalten, fordern Sie eine formularspezifische Darstellung der Ressource an. Wenn Sie es mit dem Inhaltstyp "application / x-www-form-urlencoded" oder "multipart / form-data" in die Ressourcensammlung stellen, bitten Sie den Server, diese Typdarstellung zu speichern. Der Server kann mit der gespeicherten HTML-Darstellung oder was auch immer antworten.

Möglicherweise möchten Sie auch zulassen, dass eine HTML-, XML- oder JSON-Darstellung für Zwecke einer API oder ähnlichem in die Ressourcensammlung gestellt wird.

Es ist auch möglich, Ihre Ressourcen und Ihren Workflow so darzustellen, wie Sie es beschreiben, wobei nach dem Comic veröffentlichte Cover berücksichtigt werden, für Comics jedoch ein Cover erforderlich ist. Beispiel wie folgt.

  • Ermöglicht die verzögerte Erstellung von Deckblättern
  • Ermöglicht die Erstellung von Comics mit dem erforderlichen Cover
  • Ermöglicht den Querverweis von Deckblättern
  • Ermöglicht mehrere Abdeckungen
  • Erstellen Sie einen Comic-Entwurf
  • Erstellen Sie Entwürfe für Comic-Cover
  • Comic-Entwurf veröffentlichen

GET / comic-books
=> 200 OK, Holen Sie sich alle Comics.

GET / comic-books / 0
=> 200 OK, Holen Sie sich das Comic-Buch (id: 0) mit den Covers (/ cover / 1, / cover / 2).

GET / comic-books / 0 / cover
=> 200 OK, Cover für Comic-Buch abrufen (id: 0).

GET / cover
=> 200 OK, Holen Sie sich alle Cover.

GET /
cover / 1 => 200 OK, Cover (id: 1) mit Comic (/ comic-books / 0) abrufen.

GET / comic-books / new
=> 200 OK, Formular zum Erstellen eines Comics abrufen (Formular: POST / Draft-Comic-Bücher).

POST / Draft-Comic-Bücher
Titel = foo
Autor = Boo
Verlag = Goo
veröffentlicht = 2011-01-01
=> 302 Gefunden, Ort: / Draft-Comic-Bücher / 3, Weiterleiten zum Entwurf eines Comics (ID: 3) mit Abdeckungen (binär).

GET / Draft-Comic-Bücher / 3
=> 200 OK, Holen Sie sich einen Comic-Entwurf (ID: 3) mit Umschlägen.

GET / Draft-Comic-Bücher / 3 / Cover
=> 200 OK, Cover für Draft-Comic-Buch (/ Draft-Comic-Buch / 3) abrufen.

GET / Draft-Comic-Bücher / 3 / Cover / New
=> 200 OK, Formular zum Erstellen eines Covers für Draft-Comic-Bücher (/ Draft-Comic-Buch / 3) abrufen (Formular: POST / Draft-Comic-Bücher / 3 / Abdeckungen).

POST / Draft-Comic-Bücher / 3 / Cover
cover_type =
Frontcover_data = (binär)
=> 302 Gefunden, Ort: / Draft-Comic-Bücher / 3 / Cover, Weiterleitung zum neuen Cover für Draft-Comic (/ Draft-Comic) -book / 3 / cover / 1).

GET / Draft-Comic-Bücher / 3 / Publish
=> 200 OK, Formular zum Veröffentlichen des Comic-Entwurfs (ID: 3) abrufen (Formular: POST / Published-Comic-Books).

POST / Published-Comic-Books
Titel = Foo-
Autor = Boo-
Publisher = Goo
Published = 2011-01-01
Cover_Type = Front
Cover_Data = (Binär)
=> 302 Gefunden, Ort: / Comic-Books / 3, Weiterleitung zum veröffentlichten Comic (id: 3) mit Abdeckungen.

Alex
quelle
Ich bin ein absoluter Neuling in diesem Bereich und versuche es schnell zu lernen. Ich fand das sehr hilfreich. In den anderen Blogs usw., die ich heute gelesen habe, wäre die Verwendung von GET zur Durchführung einer Operation (insbesondere einer Operation, die nicht idempotent ist) verpönt. Sollte es also nicht POST / Draft-Comic-Books / 3 / Publish sein?
Gary McGill
3
@GaryMcGill In seinem Beispiel gibt / Draft-Comic-Books / 3 / Publish nur ein HTML-Formular zurück (ändert keine Daten).
Olivier Lalonde
@Olivier ist richtig. Das Wort "Publizieren" gibt an, was das Formular tut. Da Sie die Verben jedoch auf HTTP-Methoden beschränken möchten, sollten Sie sie in einer Ressource für veröffentlichte Comics veröffentlichen. ... Wenn dies eine Website wäre, benötigen Sie möglicherweise eine URI für das Formular, um etwas zu veröffentlichen. ... Wenn die Veröffentlichungsaktion nur eine einzelne Schaltfläche auf der Comic-Seite wäre, könnte dieses Formular mit einer Schaltfläche direkt in der URI / Published-Comic-Books veröffentlicht werden.
Alex
@Alex, in der POST-Anfrage würde ich stattdessen ein 201 Created mit der URL der neuen Ressource als Location in den Antwortheadern zurückgeben.
Ismriv
2
@Stephane, Weiterleitungen machen den Controllern einfach alles einfacher. Selbst für eine API ist es einfacher, wenn der Erstellungs-Controller den Speicherort für den neuen Inhalt zurückgibt und der Show-Controller dann die Anzeige des neuen Inhalts übernimmt. Obwohl es für den Client der API schöner / einfacher ist, nur den Inhalt abzurufen und sich nicht um Weiterleitungen zu kümmern.
Alex
45

Die Behandlung von Deckblättern als Ressourcen ist definitiv im Sinne von REST, insbesondere von HATEOAS. Ja, eine GETAnfrage an http://example.com/comic-books/1würde Ihnen eine Darstellung von Buch 1 geben, mit Eigenschaften, einschließlich einer Reihe von URIs für Cover. So weit, ist es gut.

Ihre Frage ist, wie Sie mit der Erstellung von Comics umgehen sollen. Wenn Ihre Geschäftsregel lautete, dass ein Buch 0 oder mehr Umschläge haben würde, haben Sie kein Problem:

POST http://example.com/comic-books

Mit Coverless-Comic-Daten wird ein neues Comic-Buch erstellt und die vom Server generierte ID zurückgegeben (sagen wir, sie wird als 8 zurückgegeben). Jetzt können Sie Cover wie folgt hinzufügen:

POST http://example.com/comic-books/8/covers

mit der Abdeckung im Körper des Unternehmens.

Jetzt haben Sie eine gute Frage: Was passiert, wenn Ihre Geschäftsregel besagt, dass immer mindestens eine Deckung vorhanden sein muss? Hier sind einige Auswahlmöglichkeiten, von denen Sie die erste in Ihrer Frage identifiziert haben:

  1. Erzwingen Sie zuerst die Erstellung eines Covers und machen Sie Cover nun im Wesentlichen zu einer nicht abhängigen Ressource, oder Sie platzieren das erste Cover im Entity-Body des POST, der das Comic-Buch erstellt. Dies bedeutet, wie Sie sagen, dass sich die Darstellung, die Sie POST erstellen möchten, von der Darstellung unterscheidet, die Sie erhalten.

  2. Definieren Sie den Begriff einer primären oder anfänglichen oder bevorzugten oder anderweitig bezeichneten Deckung. Dies ist wahrscheinlich ein Modellierungs-Hack, und wenn Sie dies tun würden, wäre es so, als würden Sie Ihr Objektmodell (Ihr Konzept- oder Geschäftsmodell) optimieren, um es an eine Technologie anzupassen. Keine gute Idee.

Sie sollten diese beiden Optionen gegen das einfache Zulassen von Comics ohne Cover abwägen.

Welche der drei Entscheidungen sollten Sie treffen? Ich weiß nicht zu viel über Ihre Situation, aber beantworte die allgemeine Frage nach der 1 .. N-abhängigen Ressource. Ich würde sagen:

  • Wenn Sie für Ihre RESTful-Serviceschicht mit 0..N arbeiten können, ist das großartig. Möglicherweise kann eine Ebene zwischen Ihrer RESTful-SOA die weiteren geschäftlichen Einschränkungen bewältigen, wenn mindestens eine erforderlich ist. (Ich bin mir nicht sicher, wie das aussehen würde, aber es könnte sich lohnen, es zu erkunden. Endbenutzer sehen die SOA normalerweise sowieso nicht.)

  • Wenn Sie einfach eine 1..N-Einschränkung modellieren müssen, fragen Sie sich, ob Cover möglicherweise nur gemeinsam nutzbare Ressourcen sind, mit anderen Worten, sie können für andere Dinge als Comics vorhanden sein. Jetzt sind sie keine abhängigen Ressourcen mehr und Sie können sie zuerst erstellen und URIs in Ihrem POST angeben, die Comics erstellen.

  • Wenn Sie 1..N benötigen und Cover abhängig bleiben, entspannen Sie einfach Ihren Instinkt, um die Darstellungen in POST und GET gleich zu halten, oder machen Sie sie gleich.

Der letzte Punkt wird folgendermaßen erklärt:

<comic-book>
  <name>...</name>
  <edition>...</edition>
  <cover-image>...BASE64...</cover-image>
  <cover-image>...BASE64...</cover-image>
  <cover>...URI...</cover>
  <cover>...URI...</cover>
</comic-book>

Wenn Sie POSTEN, erlauben Sie vorhandene Uris, wenn Sie sie haben (aus anderen Büchern entlehnt), aber fügen Sie auch ein oder mehrere Anfangsbilder ein. Wenn Sie ein Buch erstellen und Ihre Entität kein erstes Titelbild hat, geben Sie eine Antwort 409 oder ähnlich zurück. Auf GET können Sie URIs zurückgeben.

Grundsätzlich erlauben Sie also, dass die POST- und GET-Darstellungen "gleich" sind, aber Sie entscheiden sich einfach dafür, das Titelbild weder auf GET noch auf POST zu "verwenden". Hoffe das macht Sinn.

Ray Toal
quelle