Hypermedia Links einer REST API in einem Client als ID nutzen

Schreibt man einen HTML Client für einen REST Api, so stellt sich die Frage, wie man die Urls des HTML Clients aufbaut. Auch die Url im HTML Client sollte dabei eindeutig zu einer Resource passen, damit man den Links auf die Seite des HTML Clients verschicken kann. Man muss also aus der Url des HTML Clients geschickt die Url der REST Api ableiten können. Darum soll es im Folgenden gehen.

Nehmen wir an, wir haben gerade ein Buchung durch senden eines POSTs an http://example.org/api/quotes/42/booking-creation-requests erzeugt. Wir bekommen vom der API nun folgende Resource zurück:

{
    "productCode": "TX",
    "links": [ {
        "rel": "quote",
        "href": "http://example.org/api/quotes/42"
    },
    {
        "rel": "booking",
        "href": "http://example.org/api/bookings/31"
    } ]
}

Das Problem ist nun allerdings, wie man nun mit dem obigen Ergebnis verfährt. Wie also konstruiert man nun die Urls für den HTML Client um den Benutzer im Browser die Buchung anzuzeigen. Die Url der zugehörigen Buchungsresource http://example.org/api/bookings/31 kann man auf jeden Fall nicht einfach im Browser laden da dem Benutzer dann die Resource als bspw. JSON o.ä. präsentiert werden würde – zumindest sofern die REST Api nicht auch eine HTML Präsentation anbietet.

Eine erste Idee wäre nun vielleicht, den Schlüssel der Buchungsresource zu verwenden. Ist die API Url für die Buchungsresource also bspw. http://example.org/api/bookings/31, so könnte man nun auf die Idee kommen, diese ID der Buchung (31) auch in den zugehörigen Requests des HTML Clients zu verwenden. Im folgenden Beispiel gibt es verschiedene Requests im HTML Client, da die verschiedenen Teilaspekte einer Buchung (Adressdaten, Bezahldaten, …) auf verschiedenen Seiten eingegeben werden:

  • http://example.org/bookings/31/addresses
  • http://example.org/bookings/31/payment
  • http://example.org/bookings/31/review

Hier ist nun das Problem, wie der HTML Client an diese interne ID der Buchung (31) rankommt. Dazu könnten wir sie bspw. als Feld bookingId zu der Resource hinzufügen, die wir von der REST Api zurückbekommen:

{
    "productCode": "TX",
    "quoteId": 42,
    "bookingId": 31,
    "links": [ {
        "rel": "quote",
        "href": "http://example.org/api/quotes/42"
    },
    {
        "rel": "booking",
        "href": "http://example.org/api/bookings/31"
    } ]
}

Kommt nun ein Request nach http://example.org/bookings/31/addresses beim HTML Client an, so muss er die ID aus der URL parsen und mit dieser ID die Url  http://example.org/api/bookings/31 konstruieren. Dieses führt zu folgenden Problemen:

  • Damit der HTML Client die Urls konstruieren kann, müssen  sie dort auch hinterlegt worden sein. Die Urls der REST Api können nicht so einfach angepasst werden, da sie im HTML Client auch angepasst werden müssen. Dadurch wird man recht unflexibel.
  • Die Links mit der Relation „quote“ und „booking“ an dieser Resource werden evtl. nicht mehr benötigt, da der Client sich über die Felder bookingId und quoteId die Urls für die HTML Requests zusammenbaut. Das kann dazu führen, dass die Links erst gar nicht der Resource hinzugefügt werden da man sie im HTML Client nicht benötigt – schließlich konstruiert dieser diese selber. Das Fehler der Links führt aber zu einer weniger intuitiven und inflexiblen API da nun die Urls auf jeden Fall selber zusammengebaut werden muss und nicht einfach Links gefolgt werden kann.
  • Es wird ein internes Feld an die Clients der REST Api herausgegeben, dass sie nicht zu interessieren hat und auch nur zum Konstruieren der Urls verwendet wird.
  • Erhöhte Dokumentation sofern man externe Clients hat, da die Konstruktionsvorschriften der Urls nun beschrieben werden müssen.

Eine viel bessere Lösung ist es, wenn der HTML Client stattdessen die Api Request Urls als Parameter übergeben bekommt und als ID für die Buchung verwendet. Lautet die Url für die Buchungsresource also wieder http://example.org/api/bookings/31 dann könnten die zugehörigen Requests des HTML Clients (in einem ersten Schritt) wie folgt lauten:

  • http://example.org/bookings/addresses?id=http://example.org/api/bookings/31
  • http://example.org/bookings/payment?id=http://example.org/api/bookings/31
  • http://example.org/bookings/review?id=http://example.org/api/bookings/31

Der Client muss sich die REST Urls nun nicht mehr selber zusammen sondern verwendet die als id Parameter übergebene Url. Die Lösung ist nun schon einmal deutlich besser, da wir nun auch eindeutige Urls haben aber dafür keine internen Felder in unseren Resourcen preisgeben müssen. Vom Prinzip her navigieren wir nun also über die Links in den Resourcen was uns dazu zwingt diese auch hinzuzufügen. Dieses ist gut, da Hypermedia Links zu eine flexiblen API führen.

Bei der obigen Lösung muss man nun natürlich aufpassen, dass keine Sicherheitslücke eingebaut wird. Es muss also geprüft werden, ob die an dem HTML Client übergebene Url zu einem gültigen Server führt damit ein Angreifer dem Server keine ungültige Url zu einem fremden Server unterzuschieben kann. Gefällt einem die URL als ID Parameter nicht, kann man die Url bspw. Base64 kodieren (hier muss man sie dann natürlich trotzdem noch prüfen) oder sie in der Datenbank ablegen um dann den Schlüssel für diesen Datenbankeintrag in der URL zu verwenden.

Insgesamt sollte man Hypermedia in REST Apis stark einsetzen. Dabei finde ich es häufig einen guten Ansatz nachzudenken, wie man es bei einer guten HTML Seite modellieren würde. Da folgt man schließlich auch Links die einem im Browser angezeigt werden und konstruiert keine Links per Hand. Weiter sucht man nicht im Text danach ob sich ein Produkt nun buchen lässt oder nicht sondern schaut danach, ob der „In den Warenkorb“ Button (=Link) noch aktiv ist oder nicht. Womit ich bisher immer noch unzufrieden war, war die Übersetzung zwischen den API Urls und den Urls des HTML Clients. Bisher haben wir für Aktionen wie bspw. eine Buchung auf Basis eines Angebots zu starten oder eine Buchung zu bestätigen schon Hypermedia Links hinzugefügt. Um dann aber das Ergebnis im Browser dem Benutzer anzeigen zu können, haben wir häufig die technischen Schlüssel in den Resourcen veröffentlicht und dann die Urls im Client selber zusammengebaut. Nun haben wir vor einiger Zeit diesen für uns neuen Ansatz, die Urls der API als ID zu benutzen, entdeckt und ich bin sehr zufrieden damit.

Wie immer ist dieses natürlich nicht alleine auf meinem Mist gewachsen, sondern das Ergebnis von diversen Architekturdiskussionen im Team. Vielen Dank an alle Kollegen die daran teilgenommen haben.


Gute Bücher zum Thema REST: