Links in REST Interfaces

REST ist ein wunderbares Paradigma bzw. eine Architektur für Web Schnittstellen. Und weil es eben „nur“ eine Architektur ist, bleiben einige Dinge offen und man überlässt es dem Designer, hierfür eine Lösung zu finden. Ein Punkt, den ich immer wieder für schwierig halte, ist das Design von Links oder Relationen in REST Schnittstellen.

Nehmen wir als Beispiel ein soziales Netzwerk mit Personen und Kontakten. Bei der Abfrage eines Nutzers über eine Id über die URL http://example.org/api/user/4711 sieht seine Repräsentation zum Beispiel so aus:

{
   "firstName" : "James",
   "lastName"  : "Bond",
   ...
}

Für die Repräsentation der Kontakte gibt es nun mehrere Möglichkeiten:

  • Die Repräsentationen aller Kontakte sind in der Repräsentation des Nutzers eingebettet.
  • Die Repräsentation des Nutzers enthält ein Array mit den URLs aller Kontakte.
  • Die Repräsentation des Nutzers enthält eine einzige URL, über die die Kontakte geladen werden können.

Die ersten beiden Varianten haben meines Erachtens den Nachteil, dass unter Umständen nicht benötigte Daten übertragen werden. Bei der dritten Möglichkeit wird das vermieden und der Client kann auch noch selbst über Filter steuern, welche der Kontakte er laden möchte und kann Paging benutzen, sofern es angeboten wird. Außerdem gibt es dem Client eine klare Möglichkeit für das Anlegen von neuen Kontakten: ein POST auf http://example.org/api/user/4711/contacts.

Die Repräsentation eines Nutzers könnte also so aussehen:

{
   "_links" : {
      "self" : "http://example.org/api/user/4711"
   }
   "firstName" : "James",
   "lastName"  : "Bond",
   "contacts"  : { "href" : "http://example.org/api/user/4711/contacts" } 
}

Wir benutzen als Wert für contacts ein Objekt und nicht nur einfach eine URL, damit wir später weitere Key/Value Paare hinzufügen können. Aus der obigen URL wird ja noch nicht klar, ob diese Ressource Paging unterstützt und wie die Parameter dafür aussehen. Hier könnte man zum Beispiel mit URI-Templates noch ein Muster mitgeben.

Kommen wir noch mal auf das Anlegen eines Kontaktes zurück: Was schicken wir beim POST auf http://example.org/api/user/4711/contacts mit? Einzig sinnvoll scheint mir an dieser Stelle ein Objekt zu sein, das die URL auf den Nutzer enthält, der als Kontakt gespeichert werden soll:

{
   "href" : "http://example.org/api/user/9972" 
}

Beim Laden der Kontakte eines Nutzers erhält man nun folgende Repräsentation zurück:

{
   "_links" : {
      "self" : "http://example.org/api/user/4711/contacts?offset=0&size=4",
      "prev" : "",
      "next" : "http://example.org/api/user/4711/contacts?offset=4&size=4" 
   }
   "size" : 529,
   "pageSize" : 4,
   "links" : [
      { "href" : "http://example.org/api/user/4711/contacts/526" },
      { "href" : "http://example.org/api/user/4711/contacts/6723" },
      { "href" : "http://example.org/api/user/4711/contacts/130" },
      { "href" : "http://example.org/api/user/4711/contacts/9972" }   
   ]
}

Der Wert zum Schlüssel _links enthält Meta-Daten für die Abfrage weiterer Seiten. Die Repräsentation enthält außerdem Informationen zur gesamten Anzahl aller Kontakte und der Größe der dargestellten Seite. Unter links enthält ein Array nun die URLs von den vier Kontakte, die auf dieser Seite dargestellt sind. Hier ist wieder eine Entscheidung zu treffen und zwar, welche URL eines Kontaktes hier anzugeben ist. Das Beispiel oben nimmt diese Entscheidung schon vorweg. Wir haben die folgenden Möglichkeiten:

  • Die URL des Nutzers, also zum Beispiel http://example.org/api/user/526
  • Die URL des Kontaktes mit neuen künstlichen Ids für den Kontakt relativ zum Nutzer http://example.org/api/user/contacts/1
  • Eine URL des Kontaktes mit der korrekten Id des Nutzers http://example.org/api/user/4711/contacts/526 wie im obigen Beispiel schon verwendet

Bei der ersten Möglichkeit ist die Identifikation eines Nutzers als Kontakt schwierig. Wenn wir beispielsweise den Nutzer als Kontakt löschen wollen, müssten wir das Array aller Kontakte aktualisieren (also ein PUT auf /api/user/4711/contacts) mit den URLs von allen Kontakten ohne die URL des zu löschenden Kontaktes. Die zweite Variante mit einer künstlichen Id widerspricht meines Erachtens den REST Prinzipien, die sie verlangt, dass der Server sich diese Abbildung von künstlicher Id auf richtige Id merkt (das kann auch implizit über die Datenbank erfolgen, birgt dann aber noch mehr Gefahren). Die dritte Variante hat den Vorteil, dass sie den Nutzer mit der Id 6723 klar trennt von dem Kontakt von Nutzer 4711. Ein DELETE auf diese URL löscht den Nutzer als Kontakt. Ein PUT könnte man auf diese URL ausschließen.

Die Repräsentation eines Kontaktes erfolgt natürlich als ein Nutzer. Wenn also die URL http://example.org/api/user/4711/contacts/6723 angefordert wird, erhält man:

{
   "_links" : {
      "self" : "http://example.org/api/user/6723"
   }
   "firstName" : "Eve",
   "lastName"  : "Moneypenny",
   "contacts"  : { "href" : "http://example.org/api/user/6723/contacts" } 
}

Diese Art der Darstellung scheint mir aus REST Sicht sauber und auch einfach als generelles Schema umsetzbar zu sein.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.


*