2013-10-13 2 views
23

Diese Frage bezieht sich auf das optimale REST-API-Design und auf ein Problem, bei dem ich zwischen verschachtelten Ressourcen und Sammlungen auf Stammebene wählen muss.REST-API-Entwurf: Nested Collection vs. New Root

Um das Konzept zu demonstrieren, nehme ich an, dass ich die Kollektionen City, Business und Employees habe. Eine typische API kann wie folgt aufgebaut sein. Stellen Sie sich vor, dass ABC, X7N und WWW Schlüssel sind, z. guids:

GET Api/City/ABC/Businesses      (returns all Businesses in City ABC) 
GET Api/City/ABC/Businesses/X7N     (returns business X7N) 
GET Api/City/ABC/Businesses/X7N/Employees   (returns all employees at business X7N) 
PUT Api/City/ABC/Businesses/X7N/Employees/WWW  (updates employee WWW) 

Dies scheint sauber, weil es die ursprüngliche Domänenstruktur folgt - Geschäft in einer Stadt ist, und die Mitarbeiter in einem Unternehmen sind. Einzelne Artikel sind über den Schlüssel unter der Sammlung zugänglich (z. B. ../Businesses gibt alle Geschäfte zurück, während ../Businesses/X7N das individuelle Geschäft zurückgibt). Hier

ist, was die API Verbraucher zu tun in der Lage sein muss:

  • Erhalten Unternehmen in einer Stadt (GET Api/City/ABC/Businesses)
  • Holen Sie sich alle Mitarbeiter an einem Business-(GET Api/City/ABC/Businesses/X7N/Employees)
  • aktualisieren einzelnen Mitarbeiterinformationen (PUT Api/City/ABC/Businesses/X7N/Employees/WWW)

Dieser zweite und dritte Aufruf, obwohl er an der richtigen Stelle zu sein scheint, verwendet viele Parameter, die eigentlich nicht benötigt werden erforderlich.

  • Um Mitarbeiter zu einem Geschäft zu kommen, benötigt der einzige Parameter ist der Schlüssel des Unternehmens (X7N).
  • einen einzelnen Mitarbeiter zu aktualisieren, benötigt der einzige Parameter, es ist der Schlüssel des Mitarbeiters (WWW)

Nichts im Backend-Code erfordert nicht die wichtigsten Informationen, das Geschäft zu suchen oder die Mitarbeiter zu aktualisieren. Anstatt also, erscheinen die folgenden Endpunkte besser:

GET Api/City/ABC/Businesses      (returns all Businesses in City ABC) 
GET Api/Businesses/X7N       (returns business X7N) 
GET Api/Businesses/X7N/Employees     (returns all employees at business X7N) 
PUT Api/Employees/WWW        (updates employee WWW) 

Wie Sie sehen können, habe ich erstellt eine neue Wurzel für Unternehmen und Mitarbeiter, wenn auch aus einer Domäne Perspektive sie sind eine Unter/Sub-Sub -Sammlung.

Keine der beiden Lösungen scheint mir sehr sauber zu sein.

  • Das erste Beispiel bittet um unnötige Information, sondern ist in einer Weise strukturiert, die „natürlich“ für den Verbraucher
  • Das zweite Beispiel nur für notwendige fragt (einzelne Elemente aus einer Sammlung über unteren Blätter abgerufen) erscheint Informationen, aber ist nicht in einer "natürlichen" Art und Weise strukturiert - Subsammlungen sind über Wurzeln zugänglich
  • Der einzelne Mitarbeiter root würde nicht funktionieren, wenn ein neuer Mitarbeiter hinzufügen, wie wir wissen müssen, welches Geschäft den Mitarbeiter hinzufügen, was bedeutet dieser Aufruf müsste zumindest unter dem Business-Stamm liegen, wie POST Api/Businesses/X7N7/Employees, was alles noch verwirrender macht.

Gibt es eine sauberere, dritte Möglichkeit, an die ich nicht denke?

Antwort

20

verwende ich sehe nicht, wie REST fügt eine Einschränkung hinzu, dass zwei Ressourcen nicht den gleichen Wert haben können. Die resourceType/ID ist nur ein Beispiel für den einfachsten Anwendungsfall und nicht der beste Weg, um aus einer REST-Perspektive zu gehen.

Wenn Sie sorgfältig paragraph 5.2.1.1 von Roy Fieldings Dissertation lesen, werden Sie feststellen, dass Fielding die disctinction zwischen einem Wert und einer Ressource macht. Jetzt sollte eine Ressource einen eindeutigen URI haben, das stimmt. Aber nichts verhindert, dass zwei Ressourcen denselben Wert haben:

Zum Beispiel ist die "bevorzugte Version des Autors" einer wissenschaftlichen Arbeit ein Mapping, dessen Wert sich im Laufe der Zeit ändert, während eine Zuordnung zu "der Veröffentlichung in der Tagungsband der Konferenz X "ist statisch. Dies sind zwei unterschiedliche Ressourcen, , auch wenn beide zu einem bestimmten Zeitpunkt auf denselben Wert abgebildet werden. Die Unterscheidung ist notwendig, damit beide Ressourcen unabhängig voneinander identifiziert und referenziert werden können. Ein ähnliches Beispiel aus dem Software-Engineering ist die separate Identifizierung einer versionsgesteuerten Quellcodedatei unter Bezugnahme auf die "neueste Version", "Revisionsnummer 1.2.7" oder "Revision in der Orange-Version".

Also nichts hindert Sie daran, wie Sie sagen, die Wurzel zu ändern. In Ihrem Beispiel ist ein Business ein Wert, keine Ressource. Es ist vollkommen RESTful, eine Ressource zu erstellen, die eine Liste von "jedem Geschäft in einer Stadt" enthält (genau wie Roys Beispiel, "Revisionen in der Orange-Version"), während eine Ressource "Unternehmen, die ID ist x" ist (wie "Revisionsnummer x").

Für Employees, würde ich API/Businesses/X7N/Employees als die Beziehung zwischen einem Unternehmen und seinen Mitarbeitern halten ist eine composition Beziehung, und so, wie Sie sagen, Employees kann und nur durch die Businesses Klasse Root zugegriffen werden soll. Dies ist jedoch keine REST-Anforderung, und die andere Alternative ist ebenfalls vollkommen REST-konform.


Beachten Sie, dass dies mit der Anwendung des HATEAOS Prinzip in Paar geht. In Ihrer API könnte die Liste der Unternehmen, die sich in einer Stadt befinden, (und sollte es vielleicht aus theoretischer Sicht sein) nur eine Liste von Links zu der API/Businesses sein. Dies würde jedoch bedeuten, dass die Kunden für jedes der Elemente in der Liste einen Hin- und Rückweg zum Server durchführen müssten. Dies ist nicht effizient und, um pragmatisch zu bleiben, was ich tue, ist die Darstellung des Geschäfts in der Liste zusammen mit der self Link zu der URI, die in diesem Beispiel sein würde API/Businesses.

2

Die dritte Möglichkeit, die ich sehe, ist Unternehmen und Mitarbeiter Wurzel Ressourcen und verwenden Abfrageparameter, um Sammlungen zu filtern:

GET Api/Businesses?city=ABC      (returns all Businesses in City ABC) 
GET Api/Businesses/X7N       (returns business X7N) 
GET Api/Employees?businesses=X7N     (returns all employees at business X7N) 
PUT Api/Employees/WWW        (updates employee WWW) 

Ihre beiden Lösungen verwenden Konzept der REST Unterressourcen, die erfordern, dass subresource in enthalten ist geordnete Ressource so:

GET Api/City/ABC/Businesses 

in Reaktion sollte auch Daten zurück durch:

GET Api/City/ABC/Businesses/X7N     
    GET Api/City/ABC/Businesses/X7N/Employees 

ähnlich für:

GET Api/Businesses/X7N 

, die von zur Verfügung gestellten Daten zurückgeben soll:

GET Api/Businesses/X7N/Employees 

Es wird eine Größe des großen und Zeitverhaltens macht erforderlich erhöhen zu erzeugen.

Um REST API sauber zu machen jede Ressource nur eine begrenzte URI denen neue Muster Brache haben sollte:

GET /resources 
GET /resources/{id} 
POST /resources 
PUT /resources/{id} 

Wenn Sie Links vornehmen müssen zwischen Ressourcen HATEOAS

1

Gehen Sie mit Beispiel 1. Ich würde keine unnötigen Informationen aus der Sicht des Servers kümmern. Eine URL sollte eine Ressource eindeutig aus der Sicht des Kunden identifizieren. Wenn der Client nicht wüsste, was /Employee/12 bedeutet, ohne vorher zu wissen, dass es tatsächlich /Businesses/X7N/Employees/12 ist, dann scheint die erste URL überflüssig zu sein.

Der Client sollte mit URLs und nicht mit den einzelnen Parametern arbeiten, aus denen die URLs bestehen. Daher ist mit langen URLs nichts falsch. Für den Client sind sie nur Zeichenfolgen. Der Server sollte dem Client die URL mitteilen, die er tun muss, und nicht die einzelnen Parameter, die der Client dann benötigt, um die URL zu erstellen.

11

Sie sollten REST nicht mit der Anwendung einer bestimmten URI-Namenskonvention verwechseln.

WIE die Ressourcen benannt sind, ist völlig sekundär. Sie versuchen, Namenskonventionen für HTTP-Ressourcen zu verwenden - das hat nichts mit REST zu tun. Roy Fielding selbst sagt so oft in den oben zitierten Dokumenten anderer. REST ist kein Protokoll, es ist ein architektonischer Stil.

In der Tat, Roy Fieldings sagt in seinem 2008 Blog-Kommentar (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven 2012.06.20):

„A-REST-API nicht fest Ressourcennamen oder Hierarchien (eine offensichtliche Kopplung von Client und Server) definieren muß Server müssen die Freiheit haben, ihren eigenen Namespace zu steuern, stattdessen können die Server den Clients Anweisungen geben, wie sie geeignete URIs erstellen, wie in HTML-Formularen und URI-Vorlagen in , indem sie diese Anweisungen in Medientypen und Linkbeziehungen definieren. "

Also im Grunde:

Das Problem, das Sie beschreiben, ist nicht wirklich ein Problem der REST - vom Konzept her, es ist ein Problem der Hierarchiestrukturen im Vergleich zu RELATIONAL STRUKTUREN ist.

Während ein Unternehmen "in" einer Stadt ist und somit als Teil der "Hierarchie" der Stadt angesehen werden kann - was ist mit internationalen Unternehmen, die Büros in 75 Städten haben? Dann wird die Stadt plötzlich zum Junior-Element in einer Hierarchie mit dem Geschäftsnamen auf der Senior-Ebene der Struktur.

Der Punkt ist, können Sie Daten aus verschiedenen Blickwinkeln anzeigen, und je nach Standpunkt, den Sie einnehmen, kann es am einfachsten sein, es als eine Hierarchie zu sehen. Aber die gleichen Daten können als eine Hierarchie mit verschiedenen Ebenen angesehen werden. Wenn Sie HTTP-Ressourcennamen verwenden, haben Sie eine durch HTTP definierte Hierarchiestruktur eingegeben. Dies ist eine Einschränkung, ja, aber es ist keine REST-Einschränkung, es ist eine HTTP-Einschränkung.

Aus diesem Blickwinkel können Sie die Lösung wählen, die besser zu Ihrem Szenario passt. Wenn Ihr Kunde den Namen der Stadt nicht angeben kann, wenn er den Firmennamen angibt (er weiß es vielleicht nicht), wäre es besser, den Schlüssel nur mit dem Städtenamen zu haben.Wie gesagt, ist es an Ihnen, und REST nicht im Weg stehen ...

Mehr zu dem Punkt:

Die einzige wirkliche REST Einschränkungen, die Sie haben, wenn Sie bereits verwenden entschieden HTTP mit PUT GET und so weiter, sind:

    1. ich nicht vor presumeth soll ("out-of-Band") Wissens zwischen Client und Server. *

Blick auf Ihrem Vorschlag # 1 oben in diesem Licht. Sie gehen davon aus, dass Kunden die Schlüssel für die Städte kennen, die in Ihrem System enthalten sind? Falsch - das ist nicht erholsam. Also muss der Server die Liste der Städte als Liste der Auswahlmöglichkeiten angeben. Also wirst du hier jede Stadt der Welt auflisten? Ich denke nicht, aber dann werden Sie einige Arbeit zu tun haben, wie Sie planen, dies zu tun, die uns bringt:

    1. Ein REST-API sollte fast alle seine deskriptive Mühe verbringen in den Medientyp definieren (en), die für Ressourcen darstellt und Fahranwendungszustand ...

ich denke, Sie werden die erwähnten Roy Fieldings Blog Lesen helfen deutlich heraus.

3

In einem RESTful-API URL-Design sollte recht unwichtig sein - oder zumindest ein Nebenproblem, da die Auffindbarkeit im Hypertext und nicht im URL-Pfad kodiert ist. Werfen Sie einen Blick auf die Ressourcen in der REST tag wiki hier auf StackOverflow.

Aber wenn Sie den Menschen lesbare URLs für Ihre UC entwerfen möchten, würde ich folgendes vorschlagen:

  1. Verwenden Sie den Ressourcentyp Sie erstellen/Aktualisierung/wie der erste Teil der URL Abfrage (nach Ihrem API-Präfix). Wenn jemand die URL sieht, weiß er sofort, auf welche Ressourcen diese URL verweist. GET /Api/Employees... ist die einzige Möglichkeit, Mitarbeiterressourcen über die API zu empfangen.

  2. Verwenden Sie für jede Ressource eindeutige IDs unabhängig von den Beziehungen, in denen sie sich befinden. So sollte GET /Api/<CollectionType>/UniqueKey eine gültige Ressourcendarstellung zurückgeben. Niemand sollte sich Sorgen machen müssen, wo sich der Mitarbeiter befindet. (Aber der zurückgegebene Mitarbeiter sollte die Links zu dem Geschäft (und aus Gründen der Stadt) haben, zu dem er gehört.) GET /Api/Employees/Z6W gibt den Angestellten mit dieser ID zurück, egal wo sich dieser befindet.

  3. Wenn Sie eine bestimmte Ressource erhalten möchten: Setzen Sie Ihren Abfrageparameter am Ende (statt in der hierarchischen Reihenfolge in der Frage beschrieben). Sie können die URL-Abfragezeichenfolge (GET /Api/Employees?City=X7N) oder einen Matrixparameterausdruck (GET /Api/Employees;City=X7N;Business=A4X,A5Y) verwenden. Auf diese Weise können Sie ganz einfach eine Sammlung aller Mitarbeiter in einer bestimmten Stadt ausdrücken - unabhängig von dem Geschäft, in dem sie sich befinden.

Side Knoten:

Nach meiner Erfahrung ein erstes hierarchischen Domain-Datenmodell überlebt selten zusätzliche Anforderungen, die im Rahmen eines Projekt kommen. In Ihrem Fall: Betrachten Sie ein Unternehmen in zwei Städten. Sie könnten eine Problemumgehung erstellen, indem Sie sie als zwei getrennte Unternehmen modellieren, aber was ist mit dem Mitarbeiter, der die Hälfte seiner Arbeitszeit an einem Ort und die andere Hälfte am anderen Standort bearbeitet? Oder noch schlimmer: Es ist nur klar, für welches Geschäft er arbeitet, aber es ist unklar, in welcher Stadt?

Verwandte Themen