Bereits Kunde? Jetzt einloggen.
Lesezeit ca. 14 Min.

BeerAdvisor-App mit GraphQL: Klienten am Infopoint


Linux Magazin - epaper ⋅ Ausgabe 7/2020 vom 04.06.2020

Geht es um den Datenaustausch zwischen Client und Server, kommt häufig eine REST-API zum Einsatz. GraphQL bietet eine effiziente Alternative dazu. Dieser Artikel stellt die Sprache vor.


Artikelbild für den Artikel "BeerAdvisor-App mit GraphQL: Klienten am Infopoint" aus der Ausgabe 7/2020 von Linux Magazin. Dieses epaper sofort kaufen oder online lesen mit der Zeitschriften-Flatrate United Kiosk NEWS.

Bildquelle: Linux Magazin, Ausgabe 7/2020

© alicephoto 123RF

Mit der Sprache GraphQL kann ein Client flexibel Daten von einer GraphQL-API abfragen, ähnlich wie mit SQL aus einer Datenbank. So vorzugehen zahlt sich zum Beispiel dann besonders aus, wenn eine Anwendung ihre Clients nicht kennt (zum Beispiel, weil sie eine öffentliche API bereitstellt) oder wenn es sehr viele unterschiedliche Clients mit unterschiedlichen Anforderungen gibt. Einer der ersten ...

Weiterlesen
epaper-Einzelheft 5,99€
NEWS 14 Tage gratis testen
Bereits gekauft?Anmelden & Lesen
Leseprobe: Abdruck mit freundlicher Genehmigung von Linux Magazin. Alle Rechte vorbehalten.

Mehr aus dieser Ausgabe

Titelbild der Ausgabe 7/2020 von News. Zeitschriften als Abo oder epaper bei United Kiosk online kaufen.
News
Titelbild der Ausgabe 7/2020 von Zahlen & Trends. Zeitschriften als Abo oder epaper bei United Kiosk online kaufen.
Zahlen & Trends
Titelbild der Ausgabe 7/2020 von Wie Graph-Datenbanken funktionieren und was sie leisten: Beziehungsfragen. Zeitschriften als Abo oder epaper bei United Kiosk online kaufen.
Wie Graph-Datenbanken funktionieren und was sie leisten: Beziehungsfragen
Titelbild der Ausgabe 7/2020 von Graph-Datenbank Neo4j enttarnt gefälschte Bewertungen auf Amazon: Digitale Detektei. Zeitschriften als Abo oder epaper bei United Kiosk online kaufen.
Graph-Datenbank Neo4j enttarnt gefälschte Bewertungen auf Amazon: Digitale Detektei
Titelbild der Ausgabe 7/2020 von Einfüheung: In eigener Sache: DELUG-DVD: Fedora 32 und mehr. Zeitschriften als Abo oder epaper bei United Kiosk online kaufen.
Einfüheung: In eigener Sache: DELUG-DVD: Fedora 32 und mehr
Titelbild der Ausgabe 7/2020 von RHEL 8.2: Red Hat Enterprise Linux 8.2 mit umfassenden Neuerungen in allen Bereichen: Auf ins nächste Level. Zeitschriften als Abo oder epaper bei United Kiosk online kaufen.
RHEL 8.2: Red Hat Enterprise Linux 8.2 mit umfassenden Neuerungen in allen Bereichen: Auf ins nächste Level
Vorheriger Artikel
Was die GraalVM Java-Entwicklern bringt: Heiliger Graal?
aus dieser Ausgabe

... prominenten Anbieter einer öffentlichen GraphQL-API war GitHub •. Aber auch GitLab sowie Atlassian-Produkte wie Jira • und Confluence • verfügen über ein GraphQL-API. Twitter und Facebook setzen ebenfalls auf diese Technologie, und auch viele andere Services bieten mittlerweile ein GraphQL- API an.

Die GraphQL-Spezifikation, die von der GraphQL-Foundation • als Open- Source-Lösung weiterentwickelt wird, definiert, wie die Abfragen aussehen müssen und wie ein Server diese Anfragen beantworten muss. Die API allein ist damit noch keine ausführbare Software. Es gibt aber einige Produkte, die GraphQL-APIs für bestehende Datenbanken nutzbar machen, unter anderem mit PostGraphile für PostgreSQL •. Auch die Graph-Datenbank Neo4j bringt eine GraphQL-API mit •, und für AWS gibt es mit AppSync einen Cloud-basierten GraphQL-Service …

Um Clients der eigenen Anwendung den Zugriff per GraphQL zu ermöglichen, muss die Applikation eine entsprechende Schnittstelle implementieren und zur Verfügung stellen. Das Ermitteln der abgefragten Daten ist dann Aufgabe der Anwendung. GraphQL interessiert es nicht, woher die Daten kommen: Sie können beispielsweise aus einer oder mehreren Datenbanken oder auch aus anderen (Micro-)Services stammen.

Für den Verwender der GraphQLAPI bleibt das transparent, weswegen GraphQL-APIs häufig auch als eine Art Gateway vor eine Reihe von Micro services positioniert werden. Die Entwickler der GraphQL-Schnittstelle müssen jedoch dafür Sorge tragen, dass die angebotenen Daten bei einer entsprechenden Abfrage zügig an den Client zurückgegeben werden können.

1 Der BeerAdvisor - eine GraphQL-Beispielanwendung.


Neben dem Lesen von Daten kann eine GraphQL-Schnittstelle auch das Schreiben und Verändern von Daten anbieten. Außerdem können sich die Clients am Server registrieren und von diesem über Ereignisse informieren lassen - zum Beispiel für den Fall, dass ein Datensatz hinzugefügt oder verändert wurde. Es existieren Bibliotheken und Frameworks für diverse Programmiersprachen, die es ermöglichen, eine GraphQL-Schnittstelle für die eigene Anwendung bereitzustellen.

Für Java gibt es unter anderem das GraphQL-Java-Projekt •, das wir später in diesem Artikel vorstellen. Für Scala steht das Projekt Sangria • zur Verfügung, für Javascript-Anwendungen das Apollo-Framework •. Eine Übersicht über die verfügbaren Bibliotheken findet sich auf der GraphQL-Homepage •.

GraphQL soll ermöglichen, dass ein Client immer genau die Daten von einer Anwendung abfragen kann, die er für einen Use Case gerade benötigt, zum Beispiel für eine Ansicht im Frontend. Dadurch lassen sich die Netzwerk-Requests und das übertragene Datenvolumen optimieren, da der Client niemals zu viele oder zu wenige Daten erhält.

Gleichzeitig soll der Einsatz von GraphQL auf dem Server auch eine flexible Weiterentwicklung der Anwendung erlauben, ohne dazu verschiedene Versionen der API zur Verfügung stellen zu müssen. GraphQL ermöglicht das Entkoppeln von Anbieter und Verwender der API. Der Server stellt einfach beliebige weitere Daten zur Verfügung, die der Client aber erst erhält, wenn er sie explizit abfragt. Der Server kann einem Client nicht versehentlich zusätzliche Daten schicken, auf die der gar nicht vorbereitet ist. Vielmehr pickt sich der Client genau die Teile der Daten heraus, die er für den jeweiligen Anwendungsfall benötigt.

Ein praktisches Beispiel

Eine kleine Beispielanwendung, der BeerAdvisor, soll im Folgenden die Konzepte von GraphQL praktisch verdeutlichen. Der Quellcode der Anwendung steht auf Github • zur Verfügung. Mit dieser Anwendung können Nutzer eine Reihe von Bieren bewerten und nachsehen, wo man sie erhält. Andere Nutzer können die abgegeben Bewertungen einsehen. Die App beinhaltet damit die typischen Anwendungsfälle einer Web-Anwendung.

Das Domain-Modell der Beispielanwendung besteht dabei aus vier Entitäten: dem Bier (Beer), dessen Bewertungen (Rating), dem bewertenden Anwender (User) sowie Geschäften, in denen es das jeweilige Bier zu kaufen gibt (Shop). Der BeerAdvisor-Web-Client umfasst drei Ansichten, die jeweils ganz unterschiedliche Ausschnitte aus diesem Domain-Modell benötigen.

Die Übersichtsseite zeigt beispielsweise nur den Namen der einzelnen Biere und deren durchschnittliche Bewertung an. Die Detailansicht eines Biers stellt neben diesen Informationen den Preis dar, darüber hinaus aber auch die Einzelbewertungen (aus den Entities Rating und User) sowie die Namen der Geschäfte, in denen es erhältlich ist. Die Detailseite eines Shops wiederum zeigt Informationen zu einem Geschäft (wie etwa dessen Adresse) sowie die dort erhältlichen Biere - hier aber nur jeweils den Namen und nicht den Preis, die Bewertungen und so weiter.

Mit GraphQL kann die Anwendung für jede der Ansichten genau die Informationen aus dem Modell abfragen, die sie dafür jeweils benötigt. Ändern sich die Anforderungen des Clients, kann dieser die GraphQL-Abfragen anpassen, um andere oder weniger Daten für einen Anwendungsfall zu lesen, ohne den Server dafür anpassen zu müssen.

Genauso wäre es möglich, dass ganz andere Clients hinzukommen, etwa eine mobile Variante der Anwendung. Diese Clients können dann Daten in der von ihnen benötigten Form abfragen, ohne dass der Server sie konkret für diesen Fall zur Verfügung stellen müsste.

2 Frage und Antwort einer GraphQL-Query.


Mit GraphQL Daten abfragen

Eine Abfrage in GraphQL besteht immer aus einer Operation. Davon gibt es drei Typen. Die Query dient dazu, Daten zu lesen. Mit der Mutation lassen sich Daten auf den Server schreiben beziehungsweise dort ändern. Die Subscription schließlich ermöglicht dem Client, sich für Events zu registrieren, die der Server veröffentlicht. So könnte der Server etwa einen solchen Event veröffentlichen, sobald eine neue Bewertung für ein Bier eingeht.

GraphQL-Abfragen werden in einer JSON-artigen Notation formuliert. Dabei wählt man Felder aus den von der GraphQL- API angebotenen Objekten aus. Das Beispiel aus Listing 1 zeigt die Query, die der Client ausführt, um die Übersichtsseite der Anwendung darstellen zu können. Dazu wählt er die Namen (Feld name) und die durchschnittliche Bewertung (Feld averageStars) aller vorhanden Beer-Objekte aus.

In einer echten Anwendung hätte diese Abfrage mit Sicherheit noch einige Restriktionen. So könnte der Client gezwungen sein, die Ergebnismenge einzuschränken, etwa maximal zehn Biere abzufragen. Zudem wäre es auch denkbar, dass der Client eine bestimmte Sortierung der Ergebnisse vorgeben möchte.

Im Gegensatz zu SQL gehören solche Features (Sortieren, Filtern, Limitieren) nicht zur GraphQL-Spezifikation. Anwendungen, die so etwas in ihrer API anbieten möchten, müssen diese Funktionen selbst implementieren. Dazu kann der Anwender für Felder einer API Argumente definieren, die die Anwendung beim Ausführen einer Query als Werte an die API übergibt.

In der Detailansicht eines Biers beispielsweise soll stets eine ganz bestimmte Sorte angezeigt werden - wir haben es hier also mit einer Art Filter zu tun. Das entsprechende Feld in der API (beer) bietet dazu ein Argument (beerId) an, das der Client setzen muss. Listing 2 zeigt die entsprechende Query für das Bier B1. Außerdem zeigt diese Query, wie sich Felder aus einer Hierarchie von Objekten abfragen lassen (ratings). Eine Abfrage in GraphQL besteht folglich aus einer hierarchischen Struktur von Feldern, die die Anwendung als Abfrage an einen GraphQL-fähigen Server schickt. Das erfolgt in der Regel per HTTP-POSTRequest an einen zentralen Endpunkt. Andere HTTP-Verben wie GET oder PUT spielen in GraphQL keine Rolle, ebenso wenig die HTTP-Statuscodes. Sie kommen nur bei schwerwiegenden Fehlern zum Einsatz, etwa wenn der Server nicht gefunden wird. Alle anderen Informationen werden über das Query-Ergebnis ausgetauscht, zum Beispiel durch ein eigenes, anwendungsspezifisches Error-Feld.

Eine Versionierung der API zur Laufzeit gibt es nicht, da sich diese abwärtskompatibel erweitern lässt. Der zentrale Endpunkt nimmt also die Anfrage entgegen, verarbeitet sie und liefert das Ergebnis zurück. Das Kommando aus Listing 3 zeigt, wie man mit dem Kommandozeilenwerkzeug Curl eine GraphQL-Abfrage ausführt.

Als Ergebnis liefert die Abfrage ein JSON-Objekt, das auf Root-Ebene ein Data- Feld enthält. In diesem Feld finden sich die abgefragten Daten, deren Hierarchie jener der Abfrage gleicht. Der Client weiß daher durch die Formulierung seiner Abfrage, wie die Antwort vom Server strukturell aussieht. Eine exemplarische Antwort auf die oben gezeigte BeerView- Query zeigt die Abbildung 2; die Hierarchie unterhalb von data entspricht dabei genau der abgefragten Struktur in der Query.

Tritt beim Verarbeiten der Query ein Fehler auf, zum Beispiel aufgrund einer ungültigen Query, liefert der Server zusätzlich ein Feld errors mit einer Fehlerbeschreibung zurück.

Will der Client Daten auf dem Server verändern, benutzt er eine Mutation. Die Abfrage dazu ist mit einer Query fast identisch, nur dass sie als Operationstyp mutation angibt (Listing 4). Als Argumente übergibt der Client alle Informationen, die der Server benötigt, um die Aktion erfolgreich auszuführen. Aus dem spezifischen Ergebnis einer Mutation kann der Client wiederum, genau wie bei einer Query, einzelne Felder auswählen, die er vom Server als Antwort benötigt.

Im BeerAdvisor können Nutzer über eine Mutation eine neue Bewertung (Rating) für ein Bier abgeben. Dazu muss der Client Informationen wie den Kommentar und den Benutzer übergeben. Der Server liefert für die addRating-Mutation das neue, auf dem Server angelegte Rating-Objekt zurück.

Aus diesem Objekt pickt sich der Client nun die Felder heraus, die er benötigt. Der BeerAdvisor-Client wählt hier lediglich das Feld id aus: Alle anderen Informationen der Bewertung kennt der Client bereits, sie müssen somit nicht vom Server geholt werden. Das spart Datenvolumen ein.

Die API-Definition

Eine GraphQL-API muss zwingend mit einem Schema beschrieben werden, das definiert, welche Objekte und Felder es überhaupt gibt, welche Queries angeboten werden und welche Mutations mit welchen Parametern existieren. Gegen dieses Schema validiert der Server alle eingehenden Queries und nimmt nur gültige zur Verarbeitung an.

Als gängiger Weg zur Beschreibung des Schemas dient die Schema Definition Language (SDL), die ebenfalls zu den Bestandteilen der GraphQL-Spezifikation zählt und die auch viele serverseitige GraphQL-Bibliotheken unterstützen. Eine Alternative zur SDL bietet der sogenannte Code-first-Ansatz, der das Schema aus bestehendem Source-Code ableitet, der zum Beispiel mit Annotationen erweitert ist. Dieser Weg steht ebenfalls für viele Bibliotheken zur Verfügung, ist allerdings immer bibliotheksspezifisch.

Ein Schema beschreibt die Objekte (in GraphQL Object Types genannt) samt ihrer Felder, die Clients abfragen können. Da es sich bei GraphQL um eine typischere Sprache handelt, muss man die Felder mit Typen versehen. Es gibt einige eingebaute Typen wie ID, String, Int, Boolean und außerdem Arrays beziehungsweise Listen. Die Definition eines eigenen Object Types könnte so aussehen, wie in Listing 5 gezeigt.

Das Listing definiert den Typ Rating, der aus fünf Feldern besteht. Jedes der Felder erfordert neben dem Namen den jeweiligen Typ. Dazu gehört auch die Angabe, ob es sich bei dem Feld um ein Pflichtfeld (gekennzeichnet mit einem Ausrufezeichen) oder um ein Array handelt (dann wäre der Feldtyp mit eckigen Klammern umschlossen). Die Felder beer und author zeigen auf weitere Objekttypen.

Neben den Objekttypen umfasst ein Schema noch die Operationen, die sich über die API ausführen lassen, also die angesprochenen Queries, Mutations und Subscriptions. Die Operationen heißen Root Operation Types, werden syntaktisch genauso wie Object Types in der SDL beschrieben und bestehen ebenfalls aus einer Menge an Feldern (Listing 6).

Im Typ Query ist ein Feld beer definiert, das die Beschreibung von Argumenten für Felder illustriert. Für die einzelnen Argumente muss man Name und Typ angeben, ähnlich wie bei Argumenten in Java-Methoden. Mutation und Subscription sind in einem Schema optional, die Definition des Query Type aber immer notwendig.

GraphQL-Abfragen beginnen stets bei einem Feld, das innerhalb eines der Root Operation Types definiert ist. Von dort aus lässt sich dann weiter transitiv über alle referenzierten Types wandern. Bemerkenswert ist, dass Clients die Felder auch nur in der vom Schema definierten Form abfragen können. Beliebige Kombinationen der Objekte, wie sie etwa in SQL mit Joins möglich wären, erlaubt GraphQL nicht.

Es kommt also beim Entwurf des Schemas darauf an, die nötige Flexibilität für die Clients zu berücksichtigen, indem beispielsweise der Query-Typ entsprechend viele Einstiegspunkte anbietet oder auch bidirektionale Referenzen (zum Beispiel vom Bier auf dessen Bewertungen und wieder zum Bier) zulässt. Da der Client die zusätzlichen Möglichkeiten nicht nutzen muss, wird er auch nicht mit Informationen und Daten überlastet, die er nicht braucht. Im Fall der Fälle hat er aber die Möglichkeit, die benötigten Daten flexibler auszuwählen.

Bei der Ausführung einer Query stellt die GraphQL-Laufzeitumgebung auf dem Server sicher, dass nur solche Queries verarbeitet werden, die mit dem Schema kompatibel sind. Ungültige Queries, die etwa nicht existierende Felder abfragen, weist der Server zurück. Ähnlich verhält es sich mit dem Ergebnis der Query: Der Client bekommt die Daten nur zurückgeschickt, wenn die Antwort dem Schema entspricht.

Daneben wird überprüft, ob Pflichtfelder wirklich befüllt sind und ob die zurückgegebenen Felder den erwarteten Typen entsprechen. Dadurch können Server wie Client beim Verarbeiten der Queries und der Ergebnisse sicher sein, dass sie es mit syntaktisch korrekten Daten und mit den richtigen Typen zu tun haben.

3 Der GraphQL-API-Explorer GraphiQL.


Entwickler-Werkzeuge

Unabhängig davon, wie das Schema technisch definiert wurde, lässt es sich zur Laufzeit durch eine reguläre, in der Spezifikation beschriebene GraphQL-Query abfragen, ähnlich wie man in Programmiersprachen mittels Reflection- APIs zur Laufzeit Informationen über Objekte und Typen ermitteln kann. Diese Möglichkeit hat interessante Werkzeuge für die Arbeit mit und die Entwicklung von GraphQL-Anwendungen hervorgebracht.

Ein prominentes Beispiel dafür ist GraphiQL 3. Dabei handelt es sich um einen Editor, der im Webbrowser läuft und mit dem Nutzer GraphQL-Abfragen schreiben und ausführen können. GraphiQL fragt dazu das Schema der API ab und bietet auf dieser Basis Features wie Code- Completion und Syntax-Highlighting an, so wie man das auch von IDEs anderer Programmiersprachen kennt. Der Editor steht als Web-Anwendung zur Verfügung und lässt sich in die eigene GraphQL-Anwendung integrieren, um zum Beispiel zur Entwicklungszeit Queries zu testen.

Für die Github-GraphQL-API steht der Github Explorer • zur Verfügung. Für die Jetbrains-Produktfamilie (unter anderem IDEA und Webstorm) gibt es Plugins, mit denen sich direkt aus der IDE Queries gegen eine GraphQL-API absetzen lassen. Sie unterstützen den Anwender bei der Formulierung von Queries und des Schemas mithilfe der SDL (samt Code-Completion, Refactorings und so weiter).

Beim GraphQL-Codegenerator • handelt es sich um eine Sammlung von Code-Generatoren für eine Reihe von Programmiersprachen und Bibliotheken. Auf Basis eines GraphQL-Schemas lassen sich dazu passend etwa Klassen für Java oder Typescript erzeugen.

GraphQL Server in Java

Bis hierher ging es um die Konzepte und die Spezifikation von GraphQL. Nun nimmt der Artikel in den Blick, wie sich eine GraphQL-API für eine eigene Java-Anwendung implementieren lässt. Die dabei gezeigten Konzepte lassen sich auch auf GraphQL-Bibliotheken anderer Programmiersprachen übertragen.

Als Grundlage für die Java-Implementierung dient das Open-Source-Projekt GraphQL-Java. Es hat selbst keine weiteren Abhängigkeiten, etwa vom Spring Framework oder von Java EE, lässt sich aber sehr einfach in beiden Umgebungen einsetzen. Für die Integration in Spring und Spring Boot zum Beispiel stehen zwei weitere Projekte zur Verfügung: GraphQL-Java-Spring • und GraphQL-Spring-Boot •.

Das Entwickeln einer GraphQL-Schnittstelle beginnt in der Regel mit der Definition des Schemas. Das geschieht entweder per SDL oder mit der GraphQL-Java-spezifischen programmatischen API. Dann implementiert der Entwickler DataFetcher, die festlegen, welche Daten für welches Feld der API zurückgeliefert werden. Die DataFetcher heißen in vielen anderen GraphQL-Bibliotheken übrigens DataResolver oder nur Resolver. Die DataFetcher stellen also die eigentliche Implementierung der API dar.

Ein DataFetcher ist ein einfaches Interface und verfügt nur über eine einzige zu implementierende Methode. Zur Laufzeit erhält diese Methode über den Parameter environment Informationen über die aktuelle Abfrage. Darüber lassen sich dann zum Beispiel die im Query angegebenen Argumente eines Felds auslesen.

Listing 7 zeigt exemplarisch zwei Data- Fetcher, den für das Feld beers (eine Liste aller Biere) und jenen für das Feld beer (das ein einzelnes, über das Argument beerId angegebenes Bier zurückliefert).

Solche DataFetcher muss der Programmierer zwingend für alle Felder anlegen, die ein Root Operation Type (also Query, Subscription oder Mutation) definiert. Für alle darunterliegenden beziehungsweise referenzierten Objekte sind die DataFetcher optional. Gibt es für ein Feld keinen DataFetcher, verwendet GraphQL in der Vorgabe den PropertyDataFetcher. Er versucht, ein Feld entweder über Reflexion und Namenskonventionen zu ermitteln, oder liest den Wert aus einer Map aus. Auch dieses Verhalten ähnelt den Konzepten aus GraphQL-Bibliotheken für andere Programmiersprachen.

Für die im Schema für den Beer-Typ definierten Felder name, id und price kann man auf einen DataFetcher verzichten. Das Root-Objekt (Beer) wird über einen der beiden oben gezeigten Data- Fetcher ermittelt, die jeweils ein Beer-Objekt (oder eine Liste davon) zurückgeben. Da es in der Java-Klasse Beer auch die Felder name, id und price gibt, kann ein expliziter DataFetcher dafür entfallen, da der PropertyDataFetcher diese Felder automatisch findet und abfragen kann. Dabei stellt GraphQL übrigens sicher, dass auch nur auf solche Felder zugegriffen wird, die das Schema definiert. Ein Feld, das zwar in einer Java-Klasse existiert, aber im Schema fehlt, lässt sich also nicht abfragen.

Anders sieht die Sache für das Feld averageStars im Beer-Objekt aus. Es ist in der Java-Klasse Beer nicht definiert, da der Wert dynamisch berechnet werden soll. Also gilt es, dafür einen eigenen DataFetcher zu implementieren. DataFetcher für (Unter-)Objekte sehen genauso aus wie die oben gezeigten DataFetcher für die Root Operation Types. Über die Methode getSource im übergebenen Environment lässt sich die Instanz des Objekts abfragen, das der übergeordnete Fetcher ermittelt hat. In diesem Beispiel geht es um ein Feld, das im API-Schema für das Beer-Objekt definiert ist; folglich liefert getSource eine Instanz der Java-Klasse Beer (Listing 8).

DataFetcher für Mutations werden technisch identisch implementiert. Eine Mutation etwa könnte ein Domain-Objekt verändern und über ein Repository in der Datenbank speichern. Per Definition dürfen Mutations Daten verändern, Queries aber nicht. Das Framework stellt allerdings nicht sicher, dass diese Bedingungen eingehalten werden.

Auch die DataFetcher für Subscriptions implementieren dasselbe Interface. Hier gilt es zu beachten, dass der vom Data- Fetcher zurückgelieferte Wert ein Reactive- Streams-Publisher-Objekt sein muss. Die Ereignisse sendet der Server dann in der Regel per Websocket an den Client. Beispielhafte Implementierungen dafür finden sich jeweils im BeerAdvisor-Sourcecode. Per Konfiguration werden die Data- Fetcher nun an die Felder des Schemas gebunden und über einen HTTP-Endpunkt, beispielsweise ein Servlet •, den Clients zur Verfügung gestellt.

Eine etwas höhere Abstraktionsebene als GraphQL-Java bietet das darauf aufbauende Framework GraphQL-Java- Tools …Es implementiert Resolver-Klassen statt DataFetcher. Im einfachsten Fall gibt es dabei für jeden Operation Type eine eigene Klasse, die Methoden definiert, die ebenso heißen wie die Felder der Typen. Argumente, die für ein Feld im Schema beschrieben sind, werden dabei als reguläre Java-Methodenparameter übergeben. Beim Starten der Anwendung überprüft das Framework, ob es für jedes Feld den erforderlichen Resolver gibt, und wirft andernfalls einen Fehler aus. Das folgende Listing 9 zeigt die beiden DataFetcher von oben in der Resolver-Implementierung.

Optimierte Implementierung

Die Implementierung der GraphQL-API für den BeerAdvisor fällt relativ trivial aus, weil das Datenmodell des BeerAdvisor sehr gut zur spezifizierten API passt.

Anders kann das bei bestehenden Anwendungen aussehen, für die im Nachhinein eine GraphQL-API bereitgestellt werden soll. Hier kommt es oft zu Schwierigkeiten, wenn das Datenmodell zum Beispiel nicht gut zur geplanten API passt oder die Daten für einen Query sogar aus mehreren Datenbanken oder Microservices geladen werden müssen. In solchen Fällen geraten die DataFetcher komplexer, und die Entwickler müssen sich Strategien etwa für das Caching einfallen lassen.

Ein häufig wiederkehrendes Problem ist das n+1-Problem. Der BeerAdvisor speichert die User, die Bewertungen abgeben, in einem eigenen Microservice. Wenn ein Client eine GraphQL-Abfrage absetzt, die ein Bier sowie dessen Bewertungen samt der User-Informationen ermitteln will, könnte es in einer naiven Implementierung dazu kommen, dass die Anwendung für jede Bewertung einen einzelnen Call zum User-Microservice ausführt. Dieses Problem kann man mit einem DataLoader verhindern.

Ein DataLoader fasst Aufrufe an ein externes System (oder eine Datenbank) zusammen. In unserem Beispiel würde er die IDs aller Benutzer sammeln, die ein Rating abgegeben haben. Die spezifische Implementierung des DataLoaders für genau diesen Anwendungsfall würde dann alle IDs übergeben bekommen. Sie könnte dann im besten Fall einen einzigen Aufruf an den User Microservice absetzen oder ein optimiertes SQL-Statement erzeugen und ausführen. Das Konzept der DataLoader gibt es auch für andere GraphQL-Frameworks-Implementierungen.

Eine andere Möglichkeit der Optimierung besteht darin, im DataFetcher zu überprüfen, welche Felder eine konkrete Query abfragen will. Dazu lässt sich das DataFetchingEnvironment verwenden. Abhängig von den abgefragten Feldern kann es zum Beispiel ein optimiertes SQL-Statement generieren oder die Fetch-Policies dynamisch für den Query passend setzen. Es gibt unterschiedliche Ebenen für die Optimierung der Queries.

Eine weitere Möglichkeit böte der Aufbau einer eigens für die GraphQL-API optimierten Datenbank. Die Microservices müssten in diesem Fall Änderungen an ihrem Datenbestand publizieren, sodass ein zentraler Service die Datenbank aufbauen und aktuell halten kann.

Das lässt sich zum Beispiel mit Debezium • realisieren, einem Change-Capture- Data-Tool (CDC), das Änderungen innerhalb einer Datenbank (Inserts, Updates, Delete) unter anderem über Apache Kafka anderen Anwendungen zur Verfügung stellt. Der GraphQL Prozess bekommt auf diesem Weg Änderungen mit und kann seine optimierte Datenbank stets aktuell halten, ohne dass die einzelnen Microservices angepasst werden müssten.

Fazit

GraphQL bietet eine interessante Möglichkeit, Daten gezielt vom Server zu laden, ohne dass sich Client und Server sehr eng aneinander koppeln müssen. Von der Mächtigkeit her lässt sich GraphQL aber nicht mit SQL oder ähnlichen Sprachen vergleichen: Sortierungen, Aggregationen oder eine Suche etwa fehlen gänzlich. Wer solche Features benötigt, muss sie manuell programmieren, etwa über entsprechende Felder oder Argumente in Feldern.

Es fällt in aller Regel leicht, eine Graph- QL-API für die eigene Anwendung anzubieten, und es gibt - auch dank des typisierten Schemas - sehr gute Entwicklerwerkzeuge und Bibliotheken für diverse Programmiersprachen. Um ein besseres Gefühl für die Sprache und deren Möglichkeiten zu bekommen, lohnt es sich, GraphQL einmal selbst auszuprobieren. Für komplexere Szenarien, in denen man Daten zum Beispiel aus unterschiedlichen Datenquellen laden muss, lassen sich unterschiedliche Lösungsansätze ausprobieren.

Es lohnt sich also auf jeden Fall, Graph- QL und dessen weitere Entwicklung im Auge zu behalten, selbst wenn man es (noch) nicht im eigenen Projekt einsetzen will. (jcb/jlu)

Weitere Infos und interessante Links

www.lm-online.de/qr/44934