Suche
Kolumne

EnterpriseTales: REST, ich hab da mal ne Frage

Lars Röwekamp

Ohne Frage, REST hat sich in den letzten Monaten als Programmierparadigma für verteilte Systeme, wie Microservices oder Web-APIs, mehr als etabliert. Stark vereinfacht gesprochen, kann REST als Abstraktion der Struktur und des Verhaltens des WWWs gesehen werden. Problematisch wird der ressourcenbasierte Ansatz allerdings immer dann, wenn komplexe Anfragen beantwortet werden sollen, wie uns das Beispiel des Star-Wars-Universums zeigt.

Neulich fragte mich mein ältester Sohn, ob Luke Skywalker eigentlich in allen Star-Wars-Filmen mitgespielt hat. Ich war mir zwar sicher, dass die Antwort auf diese Frage ein klares Nein ist, konnte aber auf Anhieb nicht sagen, in welchen Filmen er dabei war und in welchen nicht. Wie gut also, dass es ein nettes kleines REST-API namens SWAPI [1] gibt, das genau diese Art von Fragen beantwortet. Also schnell https://swapi.co/api/people/1 in den Browser getippt, damit ein GET auf den URI ausgelöst, und schon hatten wir die Antwort in Form eines JSON Records, wie Listing 1 zeigt.

{
  "name": "Luke Skywalker",
  "height": "172",
  "mass": "77",
  "hair_color": "blond",
  "skin_color": "fair",
  "eye_color": "blue",
  "birth_year": "19BBY",
  "gender": "male",
  "homeworld": "http://swapi.co/api/planets/1/",
  "films": [
    "http://swapi.co/api/films/6/",
    "http://swapi.co/api/films/3/",
    "http://swapi.co/api/films/2/",
    "http://swapi.co/api/films/1/",
    "http://swapi.co/api/films/7/"
  ],
  "species": [
    "http://swapi.co/api/species/1/"
  ],
  "vehicles": [
    "http://swapi.co/api/vehicles/14/",
    "http://swapi.co/api/vehicles/30/"
  ],
  "starships": [
    "http://swapi.co/api/starships/12/",
    "http://swapi.co/api/starships/22/"
  ],
  "created": "2014-12-09T13:50:51.644000Z",
  "edited": "2014-12-20T21:17:56.891000Z",
  "url": "http://swapi.co/api/people/1/"
}

 

So weit, so gut – oder eben auch nicht. Denn der REST Response brachte meinen Sohn gleich auf eine ganze Reihe neuer Ideen und Fragen. Er wollte z. B. wissen, wie denn die Episoden genau heißen, in denen Luke mitgespielt hat und mit welchem Spruch der jeweilige Film beginnt. Und dann natürlich noch, mit welchen Raumschiffen Luke in den Filmen geflogen ist und was deren Maximalgeschwindigkeit ist. Und wo wir schon dabei sind, wo kommt Luke überhaupt her, also welcher ist sein Heimatplanet?

Virtuelle Ressourcen vs. Multiple Calls

Mit ein wenig Mühe und noch mehr Tipparbeit ließen sich am Ende dank SWAPI nahezu alle Fragen meines Sohns beantworten. Es zeigte sich aber auch, dass das REST-basierte API nicht wirklich optimal für derartige Anfragen ausgelegt ist. Möchte man z. B. wissen, in welchen Filmen Luke mitgespielt hat und wie sie heißen, muss zunächst ein GET auf die Ressource people/1 aufgerufen werden, um dann im Anschluss für jeden dort referenzierten Film ein weiteres GET auf der Ressource films/x aufzurufen. Möchte man dann zusätzlich noch wissen, welche Charaktere in dem jeweiligen Film noch mitgespielt haben und in welchen weiteren Filmen sie vertreten waren, hat man schnell das Gefühl, in einer Endlosschleife gefangen zu sein. Eine eigentlich einfache Anfrage endet, bedingt durch den ressourcenbasierten Ansatz, schnell in unzähligen Client-Server-Roundtrips. Natürlich ließe sich dieses Problem durch die Einführung von virtuellen Ressourcen und entsprechenden Endpoints ein wenig entschärfen – in Anlehnung an DB Table Views. So findet man z. B. häufig in REST APIs eine virtuelle Ressource search, die zur Volltextsuche über alle Ressourcen oder zur gezielten feldbasierten Suche innerhalb einer Ressource herangezogen werden kann. Alternativ ließen sich in unserem Beispiel die Detailinformation der Ressource film in die JSON-Information der Ressource people einbetten. Das ist aber nicht wirklich REST-alike und würde den zu übertragenden Payload unnötig aufblähen.

Lesen Sie auch: Nachhaltige Webarchitekturen: Warum REST und SPAs nicht immer die Lösung sind

Und genau da sind wir schon bei einem weiteren Problem des REST-basierten (SW)API. Möchten wir z. B. von Luke lediglich die Größe und Haarfarbe erfragen, werden wir mit dem oben aufgezeigten Call gleich mit einer ganzen Reihe nicht gewünschter Informationen überhäuft – a. k. a. Payload. Um diese einzuschränken, bräuchten wir einen Query-Parameter, wie people/1?fields=a,b,c, der serverseitig zur Filterung der Rückgabefelder genutzt werden kann. Diesen stellt das API leider nicht zur Verfügung. Es müsste also entsprechend erweitert werden. Möchte man darüber hinaus noch mittels URI eine Kombination aus Suchbegriffen, Bedingungen (AND, OR) und Filteroptionen angeben, ist man schnell dabei, das Rad in Form einer eigenen REST Query Language neu zu erfinden. Aber möchte man das wirklich?

REST Query Language

Natürlich nicht, denn mit der Resource Query Language a. k. a. RQL [2], steht bereits eine fertige Open-Source-Lösung für genau dieses Problem zur Verfügung. RQL ist ein Superset von FIQL (Feed Item Query Language), das speziell zur Abfrage von Object Style Data Structures konzipiert wurde, also auch REST-Ressourcen. Möchte man z. B. alle people-Ressourcen mit blondem Haar herausfinden, die größer sind als 1,70 Meter, dann würde die Anfrage wie folgt aussehen:

GET /people?query=and(eq(hair_color,blond),gt(height,170))

Neben einer Spezifikation der Grammatik und einem JS-Parser bietet RQL Unterstützung bei der Anbindung verschiedener Datenquellen (JS Array, SQL, MongoDB, Elasticsearch). Und auch ein Java-Parser sowie ein JPA Criteria Builder stehen zur Verfügung. Mit ihrer Hilfe lassen sich RQL-Anfragen serverseitig mit wenig Aufwand direkt in Criteria-API-Anfragen umwandeln. Zugegeben, RQL nimmt einem auf jeden Fall die Arbeit ab, für das hauseigene REST API eine eigene Abfragegrammatik entwickeln zu müssen. Ganz zu schweigen von dem zugehörigen Parser und der Übersetzung des geparsten Ergebnisses in JPA. Das eigentliche Problem, nämlich die Abbildung einer komplexen Abfrage via REST API, wird durch RQL aber nicht wirklich angegangen.

GraphQL liefert Antworten

„Umdenken du musst!“ So oder ähnlich würde es wohl Yoda formulieren. Was wäre, wenn man gemäß CQRS-Pattern (Command Query Responsibility Segregation [3]) zwischen der lesenden und der schreibenden Seite/Domäne unterscheiden würde? In diesem Fall würde das REST API lediglich für das Anlegen, Ändern oder Löschen einer oder mehrerer Ressourcen verwendet werden. Komplexere Abfragen dagegen liefen über einen separaten Kanal. Dieses Vorgehen würde es uns erlauben, das lesende Modell so zu optimieren, dass wir stets mit einem Roundtrip zum Ergebnis gelangen. Genau das ist auch die Motivation für das seit 2012 von Facebook genutzte und 2015 offiziell als Open-Source-Lösung freigegebene Data-Fetching-API namens GraphQL [4], [5].

„We see a conflict between the desire to load all information in a single round trip while keeping REST resources well isolated.“ (Lee Byron, Facebook)

Die Idee hinter GraphQL ist, dass man via Abfrage beliebig auf einem Graphen navigiert und dort genau die Felder abfragt, die einen interessieren. Die Abfrage spiegelt somit nahezu 1:1 die erwartete Antwort wider. Mehrfache Roundtrips oder komplizierte, clientseitige Joins werden obsolet. Stellen wir uns als Beispiel einmal die vielen Fragen meines Sohnes zu Luke vor:

  • Wie alt ist Luke und welche Haarfarbe hat er?
  • Mit welchem Spruch wurden die Filme eröffnet, in denen er mitgespielt hat?
  • Und wie teuer waren die Schiffe, auf denen er gefahren ist?

Wie ließen sich diese Fragen nun in einer einzigen GraphQL-Anfrage verdichten? Ganz einfach, wie Listing 2 zeigt.

{
  person(name:"Luke") {
    haircolor,
    age,
    films {
      name,
      openingCrawl
    }
    starships {
      name,
      price
    }
  }
}

 

Möglich wird das, indem serverseitig die verschiedenen Datentypen (person, film, starship, …), deren mögliche Attribute inkl. evtl. Parametrisierungen sowie die Relationen der Typen untereinander deklariert werden. Hierdurch erhalten wir automatisch eine streng typisierte und introspektive Abfragesprache. Oder anders formuliert: Dank der verschiedenen Typen kann GraphQL relativ einfach die Korrektheit einer Anfrage feststellen und im Fehlerfall aussagekräftige Fehlermeldungen liefern. Darüber hinaus erlaubt es die Deklaration, eine Schnittstelle nach den möglichen Datentypen und deren Relationen abzufragen. Woher weiß nun aber der Server, wie er mit einer eingehenden GraphQL-Anfrage umgehen muss? Zum einen nutzt GraphQL das bereits erwähnte API-Schema als gemeinsame Basis für Client und Server. Zum anderen findet auf dem Server ein dreistufiger Prozess statt:

  • Step 1: parse (AST erzeugen)
  • Step 2: validate (via Schema)
  • Step 3: execute (via Resolver Functions)

Im Rahmen dieses Prozesses wird die Anfrage zunächst auf syntaktische und semantische Korrektheit geprüft und im Anschluss ausgeführt. Zur Ausführung werden serverseitig verschiedene Resolver Functions aufgerufen, deren Ergebnisse ebenfalls auf dem Server aggregiert werden. Das Ergebnis wird im Anschluss in einem einzigen Roundtrip an den Client zurückgeliefert.

Mittlerweile gibt es für nahezu jede Programmiersprache Clients, Server, DB-Anbindungen und Libraries. Eine gute Übersicht hierzu findet sich auf GitHub unter [6]. Wer auf den Geschmack gekommen ist, kann GraphQL unter [7] einmal selbst ausgiebig ausprobieren, auch am Beispiel Star Wars.

Fazit

REST-APIs bieten sich nicht wirklich für komplexe Abfragen an. Das gilt insbesondere dann, wenn die Abfrage nicht nur eine, sondern wie in unserem Beispiel gleiche mehrere Ressourcen betrifft. Virtuelle Ressourcen und eine spezielle Abfragesprache à la RQL können hier zwar helfen, lösen aber nicht das eigentliche Problem der unerwünschten Client-Server-Roundtrips. Eine mögliche Alternative stellt das Data Fetching API GraphQL von Facebook dar. Mittels JSON-Anfrage wird ein Navigationspfad inkl. der gewünschten Rückgabetypen und -felder an den Server gesandt. Dieser nutzt eigens dafür implementierte Resolver Functions, um Teilergebnisse aus verschiedenen Datenquellen abzufragen, aggregiert diese im Anschluss und schickt sie abschließend in einem einzigen Roundtrip zurück an den Client.

Lesen Sie auch: Wie Sie RESTful APIs dokumentieren

Zugegeben, GraphQL ist eine noch recht junge Technologie. Entsprechend ist auch noch das eine oder andere Optimierungspotenzial vorhanden. So ist es anders als bei REST z. B. nicht ganz trivial, sinnvolle Caches aufzubauen. Das gilt insbesondere dann, wenn Abfrageergebnisse sich nur in Teilbäumen decken. Ein weiteres Problem stellt die Versionierung dar. GraphQL kennt per Definition keine Versionierung, sondern arbeitet mit „deprecated“-Attributen. Breaking Changes in dem API können so zu einem echten Problem für den Client werden. Last but not least ist man als API-Designer bei der Verwendung von GraphQL versucht, Details des zugrunde liegenden Modells nach außen hin bekannt zu geben und somit den Server angreifbar zu machen. Die Umsetzung des Information Hiding Patterns erfordert entsprechend zusätzlichen Aufwand und Disziplin. Trotz der aufgezeigten Herausforderungen stellt GraphQL eine gute Alternative für APIs dar, die bezüglich Abfragen besonders flexibel sein müssen. Für einige der angesprochenen Probleme existieren bereits Lösungsvorschläge, so z. B. für das serverseitige Cachen von Teilergebnissen. Man darf gespannt sein, wie sich das Projekt weiterentwickelt. In diesem Sinne: Stay tuned!

Geschrieben von
Lars Röwekamp
Lars Röwekamp
Lars Röwekamp ist Gründer des IT-Beratungs- und Entwicklungsunternehmens open knowledge GmbH, beschäftigt sich im Rahmen seiner Tätigkeit als „CIO New Technologies“ mit der eingehenden Analyse und Bewertung neuer Software- und Technologietrends. Ein besonderer Schwerpunkt seiner Arbeit liegt derzeit in den Bereichen Enterprise und Mobile Computing, wobei neben Design- und Architekturfragen insbesondere die Real-Life-Aspekte im Fokus seiner Betrachtung stehen. Lars Röwekamp, Autor mehrerer Fachartikel und -bücher, beschäftigt sich seit der Geburtsstunde von Java mit dieser Programmiersprache, wobei er einen Großteil seiner praktischen Erfahrungen im Rahmen großer internationaler Projekte sammeln konnte.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.