Suchen

Eine andere Art APIs zu implementieren GraphQL als Alternative zu REST

| Autor / Redakteur: David Klassen * / Stephan Augsten

„Facebook GraphQL“ wirft wesentliche Prinzipien klassischer REST-Architekturen über Bord. Angesichts der zunehmenden Popularität von GraphQL lohnt es sich aber, einen genaueren Blick darauf zu werfen und es gegebenenfalls für eigene Projekte in Betracht zu ziehen.

Firma zum Thema

Beispiel für eine lesende GraphQL-Abfrage.
Beispiel für eine lesende GraphQL-Abfrage.
(Bild: adesso AG)

Roy Fielding veröffentlichte seine Dissertation über RESTful Applications im Jahre 2000. Heute ist sein Architektur-Stil einer der wichtigsten und am weitesten verbreiteten bei Diensten im Internet. Leider sind REST-basierte Schnittstellen nicht immer optimal auf den jeweiligen Anwendungsfall zugeschnitten.

So kann es zu dem Problem kommen, dass der Client beim Aufruf einer Schnittstelle entweder zu viele oder zu wenige Daten als Response erhält. Bei zu vielen Daten spricht man von Over-Fetching. Bei zu wenigen Daten spricht man dagegen von Under-Fetching, wobei dann mindestens ein weiterer Request notwendig wird, um wirklich alle notwendigen Daten zu bekommen.

Beide Fälle sind schlecht für die Performance der Anwendung, da unnötig große oder unnötig viele Anfragen und Antworten verschickt werden. Je nach Anwendungsfall ist dieser Performance-Verlust mehr oder weniger relevant. Beispielsweise kann es bei mobilen Anwendungen aus diesem Grund zu Verzögerungen kommen.

Die Waage zwischen Requests und Responses

Eine mögliche Maßnahme gegen dieses Problem ist es, pro Anwendungsfall eine individuelle Schnittstelle zu implementieren, so dass die Größe und Menge der Requests und Responses immer optimal ist. Dies führt allerdings fast immer zu einer unüberschaubar großen Menge an Schnittstellen für die API.

Die Schnittstellen müssen daher zwangsläufig mehr oder wenig generisch entwickelt werden, um eine gesunde Balance zwischen Abstraktion und Menge der Schnittstellen zu erhalten. Under- und Over-Fetching müssen bei diesen generischen Schnittstellen teilweise billigend in Kauf genommen werden.

Query Strings für mehr Flexibilität

Ein weiterer Lösungsansatz für die Minimierung von Under- und Over-Fetching ist die Nutzung von Query Strings. Letztere werden pro REST-Schnittstelle verwendet, um diese flexibler zu machen. So wird beispielsweise in der Abfrage …

/users?select=name,email

… mit dem Query String select die Response einer Benutzerschnittstelle /users auf den Namen und die E-Mail der Benutzer reduziert, um den Payload der Response zu verkleinern. Häufig wird mithilfe dieses Ansatzes gleichzeitig auch das Sortieren, Filtern und Paginieren implementiert. Alle eben genannten Mechanismen wirken dem Over-Fetching entgegen.

Um auch das Under-Fetching mithilfe von Query Strings zu minimieren, kann ein so genannter include- oder expand-Parameter verwendet werden. So würde beispielsweise …

/user/2?include=friends

… bewirken, dass die Response der Benutzerschnittstelle für den Benutzer mit der ID 2 nicht nur eine Liste mit Links zu den Ressourcen der Freunde des Benutzers enthält, sondern dessen bereits aufgeschlüsselte Entitäten. Somit werden weitere Anfragen eingespart, falls die Ressourcen der Freunde direkt benötigt werden.

In der Regel steigt mit der Anzahl der Query Strings auch die Komplexität bei der Implementierung. Nicht selten wird an dieser Stelle mühsam eine eigene Abfragesprache entwickelt. Dass hierbei keine Standards existieren (abgesehen von Microsofts Open Data Protocol), macht diese Aufgabe häufig schwierig und fehleranfällig.

GraphQL als Alternative

GraphQL funktioniert prinzipiell ähnlich wie der zuvor beschriebene Lösungsansatz mit den Query Strings. Der Client kann mithilfe einer standardisierten GraphQL-Abfragesprache genau spezifizieren, welche Daten er abfragen beziehungsweise manipulieren möchte.

In GraphQL werden die Daten dabei nicht wie in REST als Ressourcen gesehen, sondern als Knoten, die über verschiedene Relationen (Kanten) miteinander verbunden sind und insgesamt einen Graphen ergeben. Daher auch der Name GraphQL, wobei QL für Query Language (Abfragesprache) steht.

Technisch betrachtet wird bei GraphQL die gesamte API auf eine einzige Schnittstelle reduziert, die alle Anfragen des Clients entgegennimmt. Diese Requests beinhalten jeweils zwei unterschiedliche Arten von GraphQL-Abfragen:

  • Lesende Abfragen
  • Manipulierende Abfragen

Beispiel für eine lesende GraphQL-Abfrage.
Beispiel für eine lesende GraphQL-Abfrage.
(Bild: adesso AG)

Das vorangestellte Beispiel zeigt links eine lesende Abfrage, die den Benutzer mit der ID 42 abfragt und rechts die Antwort des Servers auf diese Abfrage. Es werden der Name, die E-Mail und die Titel seiner Blog-Beiträge abgefragt.

Dadurch, dass benötigte Felder explizit angegeben werden können, wird das Over-Fetching verhindert. Ebenso wird das Under-Fetching umgangen, da die Titel der Blogbeiträge nicht in einem weiteren Request angefragt werden müssen, sondern direkt in der ersten Abfrage angeben werden können.

Wie ist ein GraphQL-Server zu implementieren?

Beispiel für ein GraphQL-Schema mit zugehörigen Graphen.
Beispiel für ein GraphQL-Schema mit zugehörigen Graphen.
(Bild: adesso AG)

Eine GraphQL-Schnittstelle Server-seitig zur Verfügung zu stellen, ist ähnlich einfach und intuitiv wie das Erstellen von Abfragen. Im Wesentlichen müssen Schema und Resolve-Funktionen definiert werden. Das Schema definiert die Knoten und die Relationen des Graphen, der die eigentlichen Daten darstellt.

Bezogen auf das vorherige Beispiel mit dem Benutzer würde der Graph mit zugehörigen Typ-Definitionen wie folgt aussehen:

Der Query-Datentyp bildet den Einstieg des Schemas und ist somit der Startknoten im Graphen. Das bedeutet, dass jede Abfrage des Clients mit einem der Felder des Query-Datentyps beginnen muss, da die Felder die Kanten im Graphen darstellen. Vom Startknoten ausgehend können alle anderen Knoten des Graphen erreicht werden.

Während innerhalb des Schemas die Datentypen und die Relationen zueinander definiert werden, beschreiben die Resolve-Funktionen, wie beziehungsweise woher die eigentlichen Daten geladen werden. Sie legen fest, wie die Felder oder Typen des Schemas mit einem oder mehreren Backends verbunden sind. Woher genau die Daten kommen, ist nicht festgelegt. So ist es möglich, in einer Resolve-Funktion beispielsweise eine REST-Schnittstelle aufzurufen oder direkt eine Datenbank anzufragen.

Nachdem Schema und Resolver implementiert worden sind, kann die Abfrage des Clients ausgewertet werden. Dies erfolgt in drei Schritten:

  • 1. Parsen: Hier findet eine rein syntaktische Prüfung der Abfrage statt.
  • 2. Validieren: Hier wird die Abfrage mithilfe des Schemas validiert. Es wird geprüft, ob die Felder der Abfrage mit denen des Schemas übereinstimmen.
  • 3. Ausführen: Ausgehend vom Startknoten (Query-Datentyp) werden die Resolve-Funktionen der Felder ausgeführt, um das in der Abfrage angeforderte Datenobjekt nacheinander zusammen zu bauen.

Vor- und Nachteile von GraphQL

GraphQL löst unter anderem das Problem mit Over- und Under-Fetching in einer intuitiven und einfachen Art und Weise. Anders als in REST-basierten APIs muss der Entwickler keine eigene Abfragesprache entwickeln, um die API beziehungsweise Schnittstellen flexibler zu machen, sondern kann dafür auf GraphQL als Standard mit entsprechenden Referenzimplementierungen zurückgreifen.

Darüber hinaus wird gegenüber REST-basierter APIs die Kopplung zwischen Client und Server verringert, da der Client nicht mehr auf vorgefertigte und spezialisierte Schnittstellen angewiesen ist. Stattdessen können die im Kontext des Clients benötigten Daten individuell angefragt beziehungsweise manipuliert werden.

Der Client wird vom reinen Konsumenten zum Ersteller seiner eigenen Schnittstellen. Dies ist unter anderem dann hilfreich, wenn dieselbe API für unterschiedliche Plattformen – wie iOS, Android oder Web – verwendet werden soll.

Alle diese Vorteile kommen auch mit einer Reihe von Nachteilen. Themen wie beispielsweise Fehlerbehandlung, Caching oder Autorisation sind in GraphQL teilweise schwieriger umzusetzen, als in REST. Außerdem werden einige Themen in der Spezifikation nicht erwähnt, wie beispielsweise das Hochladen von Dateien. An diesen Stellen kann jedoch meistens auf Best Practices zurückgegriffen werden.

David Klassen
David Klassen
(Bild: adesso AG)

Insgesamt kann GraphQL je nach Anwendungsfall eine gute Alternative zu REST-basierten APIs sein. Besonders wenn die Schnittstellen sehr flexibel sein müssen, kann GraphQL den Aufwand der Implementierung reduzieren. In jedem Fall ist es nötig, REST und GraphQL zunächst nach Anwendungsfall beziehungsweise Anforderungen gegeneinander abzuwägen und erst dann eine Entscheidung zu treffen.

*David Klassen hat Informatik an der TU Dortmund studiert und ist aktuell Software Engineer bei der Adesso AG. Dort beschäftigt er sich neben Kundenprojekten mit der Nutzung und Weiterentwicklung von Open-Source-Software. In diesem Zusammenhang hat er bereits erfolgreich selber an einigen Open-Source-Projekten mitgewirkt.

(ID:45498642)