12. August 2020 von Muhannad Darraj
GraphQL ist flexibler, das Ende von REST-APIs?
Die Brücke zwischen Frontend und Backend wurde bisher in den meisten modernen Anwendungen durch eine REST-Schnittstelle geschlossen, um die Kommunikation mit der Geschäftslogik zu ermöglichen. Hinter modernen Softwaresystemen verbergen sich jedoch viele Daten und Funktionen, und die Menge der verwendeten Informationen nimmt stetig zu. Dies wirkt sich auch auf die Kommunikation zwischen Client und Server aus und macht diese komplexer. Um einen flexiblen Umgang mit großen Datenmengen zu ermöglichen, bietet sich GraphQL von Facebook als Alternative zu REST an.
Graph Query Language (GraphQL) ist eine Abfragesprache für APIs, die 2012 von Facebook entwickelt und 2015 veröffentlicht wurde, nachdem Facebook sie drei Jahre lang für interne Projekte verwendet hatte.
StatusQuo-API-Design nach REST
In aktuellen Projektsituationen hat sich das API-Design nach REST mittlerweile durchgesetzt, da sich diese APIs aus den von adesso eingesetzten Single-Page-Anwendungen gut anbinden bzw. nutzen lassen und von vielen Entwicklern verstanden werden. Allerdings wird das durch REST definierte Prinzip in den wenigsten Fällen wirklich durchgehalten und bringt auch einige Nachteile mit sich.
Das folgende Beispiel soll die weiteren Herausforderungen dieses Architekturansatzes verdeutlichen:
Angenommen, der Client ist eine Person namens Anna, die zu Hause sitzt und von einem Restaurant eine Pizza Margherita und von einem Supermarkt Waschmittel bestellen möchte. Anna hat REST beauftragt, diese Produkte für sie zu besorgen. Leider kann REST nur einen Weg nehmen, weshalb Anna zuerst nach der Pizza fragt. REST liefert ihr dann alles, was auf der Speisekarte des Restaurants steht. Anna nimmt die gewünschte Pizza und wirft den Rest weg. Nachdem REST das Essen besorgt hat, macht er sich auf den Weg zum Supermarkt, um das von Anna gewünschte Waschmittel zu besorgen. Wieder bringt er das gesamte Sortiment mit. Anna wählt das gewünschte Waschmittel aus und wirft auch alle anderen Produkte weg. Annas Erfahrung zeigt deutlich die folgenden Nachteile von REST:
Mehrere Endpunkte sind erforderlich
Da REST nur feste Datenstrukturen zurückliefert, müssen mehrere Endpunkte angelegt werden, um die verschiedenen Daten vom Server beziehen zu können. Beispielsweise konnte Anna ihre Wünsche nicht zur selben Zeit nennen, da REST diese Wünsche aus mehreren Endpunkten nacheinander holen musste und eine parallele Abfrage nicht möglich ist. Die Endpunkte sehen wie folgt aus:
URL/api/restaurant
URL/api/supermarkt
Overfetching
REST gibt dem Client durch eine Anfrage viele Daten zurück, so dass der Client selbst suchen und auswählen muss, welche Daten er wirklich benötigt. Dies führt zu Redundanz durch unerwünschte Daten, was sich auf die Bandbreite auswirkt und Speicherplatz und Zeit kostet.
Underfetching
REST kann immer nur einen Endpunkt abfragen, aber die Informationen können über mehrere Endpunkte verteilt sein. Daher muss der Client manchmal mehrere Abfragen nacheinander durchführen, um alle Informationen zu erhalten. REST muss also zwei Wege gehen, um Annas Anforderungen zu erfüllen. Mit einer einzigen Anfrage, z.B. an den Endpunkt /api/restaurant
, würde Anna nur die Pizza erhalten.
Beide Probleme können jedoch durch ein geeignetes API-Design gelöst werden. Hinter den REST-APIs verbirgt sich eine sehr mächtige Sprache, mit der sich der Client effizient ausdrücken kann. Die meisten Entwickler von Webanwendungen sind mit dieser Sprache vertraut.
Der Client kann ein Zeichen senden, das vom Server interpretiert wird. Beispielsweise werden die Produkte eines Supermarktes in der Datenbank mit EAN-Codes bezeichnet. Dazu übergibt der Client der REST-API den EAN-Code des gewünschten Produkts. Die REST-API gibt den Code an den Server weiter, der dann in der Datenbank nach dem gewünschten Produkt sucht. Kann der Server die Anfrage des Clients erfüllen, übernimmt die REST-API die Daten und gibt sie an den Client zurück. Andernfalls gibt der Server eine entsprechende Fehlermeldung an die REST-API zurück, z.B. wenn das Produkt nicht mehr verfügbar ist. Die Suche nach möglichst effizienten Namen für Endpunkte kann viel bewirken und zu einem besseren API-Design führen.
Das bedeutet, dass Anna genau beschreiben sollte, wonach REST den Server fragen soll. Geeignete Endpunkte sehen zum Beispiel so aus
URL/api/supermarkt/produkte/{ean}
URL/api/restaurant/pizza/margherita
Mit dieser Abfrage würde Anna nur das gewünschte Produkt und die gewünschte Pizza erhalten. Durch dieses Design werden Über- und Unterbestellungen vermieden. Aber was passiert, wenn Anna noch weitere Produkte haben möchte? Dann müssten weitere API-Aufrufe gemacht werden, genau wie beim Supermarkt. Da die API des Restaurants nicht auf Standards wie EAN-Codes zurückgreift, müssen wir uns hier überlegen, wie die anderen Ressourcen identifiziert werden können. Aber was ist, wenn Anna eine ganze Liste von Produkten aus verschiedenen Ressourcen hat? Dann brauchen wir auch mehrere REST-Endpunkte. Das heißt, je mehr Ressourcen aus dem Backend angefordert werden, desto mehr Endpunkte werden benötigt. Auch hier haben wir wieder das Problem, dass mehrere Endpunkte benötigt werden.
Die Lösung kommt mit GraphQL
Facebook hat sich 2012 den genannten Herausforderungen gestellt und mit GraphQL eine Spezifikation entwickelt. Rund um diese Spezifikation haben sich mittlerweile einige Implementierungen und ein komplettes Ökosystem gebildet, die den Einsatz in aktuellen Projekten analog zu REST unterstützen. GraphQL unterscheidet sich von REST-APIs durch folgende Punkte. Diese Anpassungen ermöglichen ein API-Design, das den Herausforderungen von REST gerecht wird.
Single Endpoint
GraphQL hat nur einen Endpunkt. Daher sind keine Endpunkte für PUT, DELETE und andere Methoden erforderlich. Die Anfragen werden durch Queries beschrieben, so dass nur eine Anfrage an den Endpunkt gesendet werden muss. Diese Abfrage wird in einer speziellen Abfragesprache, ähnlich SQL, beschrieben. Sie wird dann vom Server interpretiert. Ein Beispiel ist die Anfrage URL/graphql?query=annaswuensche. In “annaswuensche” kann der Client alle Daten beschreiben,die benötigt werden.
GraphQL als Wrapper
Ein weiteres wichtiges Feature von GraphQL ist, dass GraphQL als Wrapper für verschiedene Ressourcen erstellt werden kann. Dies ist besonders dann von Vorteil, wenn die Informationen auf verschiedenen Servern verteilt sind, beispielsweise bei Datenbanksystemen.
Übersichtlich
Der Client muss in den zurückgelieferten Daten nicht nach seinen Anforderungen suchen, da er nur die Attribute eines oder mehrerer Objekte erhält, die er angefordert hat. Nachfolgend ein Beispiel für eine Query in GraphQL aus der Versicherungsbranche: Angenommen, der Client möchte folgende Query: “Die Telefonnummer eines bestimmten Versicherungsnehmers sowie die IDs und Telefonnummern seiner Partner”.
{
versicherungsnehmer(id: 1234) {
telefonnummer
partnerdaten {
id
telefonnummer
}
}
}
Mit dieser Abfrage haben wir das Overfetching unnötiger Attribute vermieden. Versicherungsnehmer- und Partnerdaten können weitere Attribute wie Name, Geburtsdatum, Partnerrolle usw. haben. Wir erhalten jedoch ein JSON-Objekt “data”, das nur die gewünschten Attribute enthält.
Es ist nicht alles Gold was glänzt
Obwohl GraphQL viele gute Lösungen für bekannte Probleme von REST Web Services bietet, bringt es auch neue Herausforderungen mit sich. Aufgrund von Operationen wie (Types, Queries, Mutators, Resolvers,...) ist der Einstieg in die Entwicklung einer GraphQL-API deutlich komplizierter. Darüber hinaus macht GraphQL viele Operationen schwieriger, wie z.B. die Komplexität des Cachings und des Hochladens einer Datei. Die Definition einer REST-API ist wesentlich einfacher und den meisten Developern bekannt, wodurch viel Aufwand und Zeit gespart werden kann.
Komplexität im Caching
Ein sehr wichtiges Feature, das sowohl dem Client als auch dem Server große Flexibilität bietet, ist das HTTP-Caching. Bereits ermittelte Ergebnisse auf Anfragen des Clients können zwischengespeichert werden, um schnellere Antwortzeiten zu ermöglichen. GraphQL kann jedoch nicht von den Möglichkeiten des HTTP-Caching profitieren, da alle Anfragen über einen einzigen Endpunkt bedient werden. Der Vorteil von GraphQL (Single Endpoint) wird hier zum Nachteil, da die in der URL enthaltenen Query-Requests nicht eindeutig sind. Der Server weiß also nicht, welche Daten der Client zuvor angefordert hat. Daher ist Caching in GraphQL nur schwer zu implementieren. Lösungen bieten Tools wie DataLoader, um das Caching in GraphQL zu vereinfachen. Im Gegensatz dazu ist dieses Feature bei REST deutlich einfacher zu implementieren.
Workaround, um eine Datei hochzuladen
Das Hochladen einer Datei mit GraphQL ist nicht direkt möglich, wie es beispielsweise bei REST-APIs der Fall ist. Die “Mutations” in GraphQL können Dateien nicht interpretieren, daher müssen Entwickler die Datei zunächst in Base64 konvertieren und die Kodierung der Mutationsanfrage übergeben. Alternativ kann die Datei über die REST-API hochgeladen werden, wobei GraphQL als Wrapper verwendet wird, um die URL der Mutationsanfrage zu übergeben. Andere Lösungen sind bekannt, aber in den meisten Fällen ist dieser Prozess komplizierter und aufwändiger als über REST-APIs.
Anfragen können hohe Last verursachen
In der Graphentheorie ist ein Baum ein spezieller Graphentyp. Auch eine Abfrage kann als Baum dargestellt werden. GraphQL beschränkt die Tiefe eines Baumes nicht, was dem Client eine gewisse Freiheit gibt. Auf der anderen Seite bedeutet dies aber auch, dass durch eine Abfrage große Datenmengen eines Zweigs geladen werden können, bis ein Blatt erreicht ist. Dadurch wird der Server mit vielen Daten belastet und die Bearbeitung einer Anfrage verlängert sich. Diese Anfragen können auch zu einem Serverausfall führen.Verschiedene Endpunkte in REST-APIs haben also nicht nur Nachteile, sondern auch viele Vorteile. Eine komplexe Anfrage kann mit REST-Endpunkten einfach und problemlos implementiert werden, da ein Endpunkt feste Datenstrukturen zurückgibt, so dass ein Entwickler die Datenmenge begrenzen und den Server schützen kann.
Wie wir bereits gesehen haben, gibt es auch bei GraphQL einige Herausforderungen, die Facebook mittlerweile mit neuen Lösungsansätzen zu lösen versucht. Diese erhöhen die Komplexität der Implementierung. Um eine leistungsfähige, fehlerfreie und saubere Graph-API zu erhalten, bedarf es Know-How und Aufwand.
GraphQL löst REST-APIs nicht ab
Betrachtet man das “Richardson Maturity Model” von Leonard Richardson, so stellt man fest, dass GraphQL nur mit REST-APIs der Stufe 2 des beschriebenen Modells vergleichbar ist und sich vom eigentlichen REST-Konzept distanziert. Das bedeutet, dass wir erst dann von einer “vollständigen” REST-API sprechen können, wenn diese den höchsten Reifegrad, also Level 3, erreicht hat. Tatsache ist aber leider, dass viele Entwickler nicht die “vollständige” REST-API verwenden, sondern die REST-API auf Level 2. Daher ist es wichtig, das Konzept von REST nicht zu verwässern.
Kein Medikament ist ohne Nebenwirkungen. Dasselbe gilt auch für Technologien. Nicht jede Technologie ist für jede Herausforderung im Projekt geeignet und es liegt am Softwareentwickler, die passende Lösung zu finden. GraphQL ist aufgrund seiner Flexibilität im Umgang mit großen Datenmengen deutlich besser geeignet als REST-APIs, wenn die Applikation mit Massendaten arbeitet und diese aus verschiedenen Ressourcen bezieht. Bei kleinen Anwendungen hingegen wird GraphQL fast schon zum Overkill für die Anwendung. Hier sollten REST-APIs verwendet werden, um unnötige Komplexität durch GraphQL zu vermeiden. Wie bereits im Abschnitt “Anfragen können hohe Last verursachen” beschrieben, ist REST bei komplexen Anfragen GraphQL überlegen, da mit REST-APIs sowohl die Performance als auch die Serverlast solcher Anfragen einfacher in einem kontrollierten Rahmen gehalten werden kann, während man bei GraphQL schnell den Überblick verlieren kann und es durch eine schlechte Implementierung zu Serverausfällen kommen kann.
Ein Ausblick – Symbiose beider Ansätze
Da die meisten modernen Anwendungen bereits mit REST entwickelt wurden, kann eine Kombination von GraphQL und REST-APIs sinnvoll sein. In diesem Fall ist es nicht notwendig, die REST-APIs nach GraphQL zu übersetzen, um die Vorteile von GraphQL zu nutzen.
Wir haben gesehen, dass GraphQL Daten von verschiedenen Ressourcen abrufen kann. Dies können wir uns zunutze machen, indem wir GraphQL als Gateway verwenden, um bestehende REST-Endpunkte aufzurufen und Informationen abzufragen. Auf diese Weise kann der Client von der Flexibilität von GraphQL profitieren, ohne REST in GraphQL übersetzen zu müssen.