Echtzeit-Kommunikation mit GraphQL I/O

Etablierte Anwendungsschnittstellen sind zeitgemäßen Anforderungen kaum noch gewachsen. Mit GraphQL steht allerdings eine veritable Sprache für Omnichannel- und Echtzeit-Kommunikation in den Startlöchern.

Sprachen  –  16 Kommentare

Im Zuge der Digitalisierung haben mehr und mehr Unternehmen verstanden, dass ein positives Kundenerlebnis ein entscheidender Vorteil im Wettbewerb ist. Wurde das Kundenerlebnis (User Experience) anfangs noch auf schicke Bedienoberflächen mit imponierenden Effekten reduziert, sind weitere Faktoren in den Mittelpunkt der Aufmerksamkeit gerückt. Mit dazu gehören eine Omnichannel-Strategie und Echtzeit-Aktualisierung der präsentierten Daten.

Sowohl Omnichannel-Strategie als auch Echtzeit-Aktualisierung stellen Architekten und Entwickler vor eine Hürde. Etablierte Anwendungsschnittstellen wie RPC, SOAP oder REST sind dafür entweder nicht geeignet oder erfordern einen hohen Entwicklungsaufwand. Abhilfe schafft das von Facebook stammende GraphQL in der Variante GraphQL I/O. GraphQL als Schnittstelle erfüllt alle Voraussetzungen des stark variierenden Datenbedarfs einer Omnichannel-Strategie – und GraphQL I/O ermöglicht die einfache Echtzeit-Aktualisierung der Daten auf Basis von GraphQL.

Was ist Echtzeit?

Echtzeit meint lediglich, dass ein System in der Lage ist, innerhalb eines zuvor als akzeptabel festgelegten Zeitrahmens auf ein bestimmtes Ereignis zu reagieren. Die Größe des Zeitrahmens hängt von verschiedenen Faktoren ab und kann wenige Mikrosekunden bis Stunden betragen.

Im Umfeld der digitalen Transformation zählt vornehmlich die User Experience, die deshalb entscheidend den Zeitrahmen für Echtzeit begrenzt. 0,1 Sekunden gelten als unmittelbare Reaktion und bilden den für Tastatur-, Maus- oder Touch-Eingaben relevanten Zeitrahmen. Verzögerungen bis eine Sekunde nehmen die Anwender bereits wahr, aber das Gefühl eines flüssigen Arbeitsablaufes bleibt bestehen. Noch als akzeptabel und interaktionsfähig gilt ein Zeitrahmen bis zu zehn Sekunden. Die Anwender können zwar weiterarbeiten und ihre Gedankengänge halten, die User Experience leidet aber bereits erheblich. Ab zehn Sekunden haben Anwender dann das Gefühl, aufgehalten zu werden und suchen sich womöglich alternative Beschäftigungen.

Anforderung: Omnichannel und Echtzeit

Die digitale Transformation hat einige entscheidende Paradigmenwechsel angestoßen. Unter anderem ist der Anwender jetzt der Ausgangspunkt aller Überlegungen zu Unternehmensstrategie und Ausgestaltung betrieblicher Informationssysteme geworden. Das ist daran zu erkennen, dass Unternehmen nicht mehr einfach festlegen können, auf welchen Wegen ihre Kunden und Anwender mit ihnen in Kontakt treten sollen. Stattdessen müssen sie ihren Kunden dorthin folgen, wo diese sich jetzt oder in Zukunft befinden.

Der Kontakt läuft dabei mittlerweile über viele Kanäle parallel, etwa online auf der Shopseite, offline im Ladengeschäft, über die Unternehmenswebseite, soziale Medien und viele mehr. Ziel muss sein, dass Kunden und Anwender die Kanäle auf möglichst vielen Geräteplattformen nutzen und nahtlos zwischen ihnen wechseln können, ohne den Wechsel zu bemerken. Der sie individuell umgebende Kontext aus Daten muss dazu über alle Kanäle hinweg konsistent sein. So ist sichergestellt, dass die Kunden weder mit lückenhaften noch mit widersprüchlichen Informationen konfrontiert werden oder dieselben Informationen mehrmals eingeben müssen. Diese Strategie der nahtlos verflochtenen, konsistenten Kanäle läuft unter der Bezeichnung Omnichannel (siehe Kasten).

Der feine Unterschied: Cross-Channel vs. Omnichannel

Mitunter fällt die Abgrenzung zwischen Cross-Channel und Omnichannel vage aus. Während Cross-Channel klar auf eine feste Auswahl verknüpfter Kanäle abzielt, bezieht sich Omnichannel auf alle existenten und noch kommenden Kanäle. Alle Kanäle sind bei Omnichannel so miteinander verknüpft, dass die Kunden nicht mehr merken, dass sie mit dem Unternehmen auf mehreren verschiedenen Kanälen in Kontakt stehen. Die verschiedenen Kanäle rücken in den Hintergrund, die Wahrnehmung des Unternehmens als Marke steigt.

Jeder Kanal stellt allerdings eigene Anforderungen an betriebliche Informationssysteme. Die Datenzugriffsmuster, die die Anforderungen beschreiben, hängen dabei nicht nur vom Kanal, sondern auch von der Rolle des Anwenders und natürlich der Geräteplattform ab. Hinzu kommt, dass je nach Plattform und Betriebssystem womöglich mehrere eigenständige Clients zu entwickeln sind, die wiederum individuelle Daten und Datensichten benötigen. Das erfordert eine hochgradig flexible Anwendungsschnittstelle, die sich einfach an die verschiedenen Datenzugriffsmuster anpassen lassen muss.

Dazu gesellt sich noch die Echtzeit-Aktualisierung von Daten, die ebenfalls ein positives Kundenerlebnis prägt und die Produktivität erheblich steigern kann. Nimmt ein Anwender relevante Änderungen an Daten vor, übermittelt der Client sie automatisch zum Server. Gleichzeitig überträgt der Server relevante Änderungen sofort an die betroffenen Clients. Dadurch spiegeln sich nicht nur die Änderungen des Anwenders auf allen seinen Kanälen und Geräten zeitnah wider, sondern auch solche, die von anderen Anwendern vorgenommen wurden. Im Rahmen betrieblicher Informationssysteme lassen sich somit Race Conditions und Inkonsistenzen verringern. Locking kann zudem ganz oder zumindest teilweise entfallen. Und nervige Systemmeldungen wie "Die Bearbeitung ist durch einen anderen Anwender blockiert!" gehören der Vergangenheit an.

Etablierte Schnittstellen sind unpassend

Wie muss eine Anwendungsschnittstelle ausgestaltet sein, damit sich darüber eine Omnichannel-Echtzeit-Strategie sinnvoll fahren lässt? Echtzeit erfordert von der Anwendungsschnittstelle Push- statt Pull-Kommunikation. Denn die entbindet den Client von der Aufgabe, den Server ständig nach neuen Daten fragen zu müssen. Stattdessen lässt sich der Client die Daten so oft wie möglich aktiv vom Server anliefern. Alternativen in Form konstanten Pollings durch die Clients würden unnötige Last auf der Infrastruktur erzeugen. Mit Blick auf mobile Geräte fielen für den Anwender durch das Polling auch potenziell Kosten durch getaktete Verbindungen an und der Akku wird strapaziert.

Ohnehin sind heute die mobilen Geräte und Funkverbindungen der wichtigste Zugangspunkt zu betrieblichen Informationssystemen. Bandbreite, Latenzen und Kosten sind deshalb entscheidende Faktoren der Datenzugriffsmuster. Client und Server sollten deshalb stets einen angemessenen Datenaustausch pflegen. Sprich: Der Client sollte den benötigten Umfang und Detailgrad der Daten festlegen können. Denn je kleiner etwa der Bildschirm, desto weniger Inhalte und Daten lassen sich überhaupt darstellen. Desto kleiner sollten dann auch die einzelnen Datenlieferungen ausfallen.

Bei Funkverbindungen kommt auch zum Tragen, dass sich innerhalb einer WLAN- oder LTE-Funkzelle oft viele Geräte tummeln. Je mehr Geräte angemeldet sind und Daten austauschen, desto geringer fällt die verfügbare Bandbreite je Gerät aus. Je mehr Daten dann übertragen werden müssen, desto länger dauert es, bis die Übertragung abgeschlossen ist. Die Verzögerungen sind für die Anwender spürbar.

Der Omnichannel-Strategie ist auch geschuldet, dass die Schnittstelle bequem anpassbar und erweiterbar sein muss. Nur so lässt sich für zukünftige Kanäle und Clients der angemessene Datenaustausch garantieren. Sollte sich überdies das Datenmodell einmal ändern, dann müssen diese Änderungen wiederum schnell über die Schnittstelle reflektiert werden können.

Schlussendlich muss sich die Echtzeit-Kommunikation auch möglichst einfach und konsistent durch die Architekten und Entwickler umsetzen lassen, sowohl auf Client- als auch Serverseite. Wäre der Entwicklungsaufwand zu groß oder müssten zu viele Workarounds geschaffen werden, dann wird die Schnittstelle unzuverlässig und womöglich unwartbar.

Auf die Anforderungen variabler Datenzugriffsmuster (Englisch: data access pattern), adäquater Datenkommunikation, Echtzeit-Aktualisierung, Erweiterbarkeit und einfache Implementierung hin abgeklopft, stellt sich heraus: Keine der etablierten Schnittstellen RPC, SOAP oder REST kann sie vollständig erfüllen.

RPC, der Dinosaurier

Remote Procedure Call (kurz RPC) war die erste API für verteilte Systeme. Sie entstand Ende der 1970er Jahre und ist somit der Urahn der etablierten Schnittstellentechnologien. Damals war an datenbereitstellende Services noch gar nicht zu denken. Stattdessen war die Motivation hinter RPC, auf einem fernen System eine Funktion oder Prozedur so aufzurufen, als wäre sie ein Bestandteil des eigenen Systems. RPC ist deshalb rein funktionsorientiert. Einfacher sowie eingängiger lässt sich das funktionsorientierte Programmierprinzip nicht auf verteilte Systeme übertragen.

Die Anzahl der Parameter und ihre Reihenfolge sind bei RPC exakt definiert. Gleichzeitig sind Parameter und Rückgabewerte typisiert und die ausgetauschten Daten dementsprechend eindeutig. Vorausgesetzt die API ist feingranular und umfangreich aufgebaut, ist der Client in der Lage, ganz spezifische Daten abzurufen und sie je nach Bedarf zusammenzustellen. Variierende Data Access Pattern stellen für RPC in dieser Hinsicht weniger ein Problem dar. Aber: Die Granularität muss auf Serverseite explizit so vorgesehen und implementiert sein.

Entwickler und Architekten müssen sich für die Aggregation der Daten aber ein genaues Verständnis der Domäne und der Schnittstelle erarbeiten, um spezielle Aufrufreihenfolgen einhalten oder die richtigen Informationen übergeben zu können. Ein Beispiel dafür sind initiierende RPCs, die einen Handler oder ein Kontextobjekt erzeugen, die im weiteren Verlauf als Parameter verwendet werden müssen. Je mehr Funktionsaufrufe zu koppeln sind, desto höher wird der Kommunikationsaufwand ausfallen. Damit fällt die Datenkommunikation unter Umständen nicht immer adäquat aus.

Insgesamt entsteht zwischen Client und Server aufgrund der Funktionsorientierung überdies eine sehr enge Kopplung. Die Konsequenz: Muss man die Schnittstelle einmal anpassen oder erweitern, lässt sich das oft nur durch zusätzliche Funktionen mit angepassten Parameterfolgen umsetzen. Damit die Schnittstelle nicht wild wuchert, müssen diese Ergänzungen maß- und planvoll sein. Denn nachträgliche Änderungen an bereits existierenden Funktionen führen dazu, dass alle Clients überarbeitet und neu ausgerollt werden müssen. Je mehr Clientimplementierungen es gibt, desto umfangreicher fallen die Nacharbeiten aus. Die Erweiterbarkeit der Schnittstelle ist also gegeben, aber an bestimmte Voraussetzungen wie die Orthogonalität geknüpft. Bei RPC gilt das Pull- statt Push-Prinzip.

SOAP, das XML-Ungetüm

Im Zuge der großen Aufmerksamkeit für XML entstand Ende der 1990er-Jahre SOAP, das im Rahmen serviceorientierter Architekturen noch immer beliebt ist. SOAP ist nichts anderes als eine mit XML abstrahierte und kombinierte Variante von RPC. Im Vordergrund steht zwar mehr der Austausch klar strukturierter Daten zwischen unterschiedlichen Systemen, der entfernte Funktionsaufruf ist dennoch allgegenwärtig.

Wie bei RPC ist die Cliententwicklung einfach und eindeutig, allerdings abhängig von der Serverentwicklung. Je umfangreicher, feingranularer und somit aufwändiger die Schnittstelle auf Serverseite implementiert ist, desto vielfältiger sind die Datenstrukturen, die dem Client zur Verfügung stehen.

Der Schnittstellenkontrakt regelt dabei, welche Daten in welchem Detailgrad auf welche Art ausgetauscht werden können und was die Daten bedeuten. Mit "Kein Kontakt ohne Kontrakt!" lässt sich deshalb das allgemeine Vorgehen beschreiben. Sowohl der Client als auch der Server können sich auf den Schnittstellenkontrakt verlassen. Änderungen sind nur erlaubt, wenn der Schnittstellenkontrakt zuvor entsprechend angepasst wurde. Diese Eineindeutigkeit ist grundsätzlich gut. Sie bedeutet aber auch: Sollte der Client aufgrund veränderter Data Access Pattern anders strukturierte und detaillierte Daten benötigen, müssen Entwickler vor der Clientimplementierung erst den Schnittstellenkontrakt festlegen und danach die entsprechende Schnittstelle auf Serverseite implementieren.

Aufgrund des Einsatzes von XML ist SOAP über die Maßen geschwätzig. Eine SOAP-Nachricht besteht immer aus einem Umschlag, der bestimmte Mindestinformationen verlangt. Im Umschlag verpackt ist dann die eigentliche Nutzlast aus Funktionsname, Parametern und Daten, wiederum in XML-Notation. Diese kann mitunter langatmig und unverhältnismäßig ausfallen, etwa durch lange Bezeichner. Das führt dazu, dass für den Abruf von wenigen Byte fachlicher Daten mehrere Hundert Byte technischer Overhead entstehen. Allein dadurch ist SOAP für den Einsatz über Mobilfunknetze und andere Kommunikationswege mit geringer Bandbreite ungeeignet. Ferner gilt auch für SOAP: Pull statt Push.

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<ebl:RequesterCredentials soapenv:mustUnderstand="0" xmlns:ebl="urn:ebay:apis:eBLBaseComponents">
<ebl:eBayAuthToken xmlns:ebl="urn:ebay:apis:eBLBaseComponents">token</ebl:eBayAuthToken>
</ebl:RequesterCredentials>
</soapenv:Header>
<soapenv:Body>
<GeteBayOfficialTimeRequest xmlns="urn:ebay:apis:eBLBaseComponents">
<DetailLevel>ReturnAll</DetailLevel>
<Version>423</Version>
</GeteBayOfficialTimeRequest>
</soapenv:Body>
</soapenv:Envelope>

(SOAP-Beispiel: Aus der offiziellen Dokumentation der eBay-Verkaufs-API stammt diese SOAP-Nachricht. Ziel der Nachricht ist der Abruf der offiziellen eBay-Zeit.)

REST, der Missverstandene

Wie eine Art Gegenentwurf zu SOAP stellt sich das mittlerweile überall anzutreffende REST dar – oder um genau zu sein der Architekturstil Represesentational State Transfer. Dieser Architekturstil legt unter anderem eine über alle Anwendungen hinweg einheitliche, ressourcenorientierte Schnittstelle zum Zugriff auf Daten fest.

Das Datenformat für den Datenaustausch darf der Client frei wählen, ebenso den Transportweg, auch wenn meist JSON und XML über HTTP zum Einsatz kommen. Weil HTTP gut standardisiert ist, vermeidet man bei REST üblicherweise Schnittstellenkontrakte. Die Adressierung der Ressourcen erfolgt per allseits geläufigem Uniform Resource Identifier (kurz URI). Wegen dieser Vorzüge lässt sich REST intuitiv und schnell implementieren, was einen Code-First-Ansatz fördert. Entwickler sind also in der Lage, kurzfristig zusätzliche Ressourcen bereitzustellen. Stark variierende Data Access Pattern kann REST auf diese Weise nicht direkt bedienen, aber die Cliententwicklung wird auch nur geringfügig verzögert.

Genau wie SOAP kann REST allerdings geschwätzig sein. Schließlich sind die Struktur der Daten und die Beziehungen der Datenfelder zueinander starr. Aufrufparameter ermöglichen in der Regel nur eine Reduzierung der Informationen, etwa durch Suche, Filterung, Sortierung, Paginierung oder Ausschluss von Feldern. Neue Aggregate lassen sich erst nach dem Datenabruf aufbauen. Das bedeutet: Ein Client muss unter Umständen viele Ressourcen mit teilweise irrelevanten Details nacheinander abrufen, um daraus die für ihn passende Auswahl an Daten zu einer neuen Struktur zusammenstellen zu können. Dieser Overhead an fachlichen Daten kann mitunter gravierend ausfallen und ist im Umfeld geringer Bandbreiten und hoher Latenzen für Echtzeitanwendungen nicht tragbar.

Ein vermeintlicher Vorteil von REST ist gleichzeitig auch ein Nachteil. Denn REST ist eher unterspezifiziert. Die Art und Weise der Umsetzung ist nämlich nicht weiter vorgeschrieben, sodass jeder die Implementierungsstrategie frei wählen darf, etwa für Error-Handling, Authentifizierung oder Datenrepräsentation. Das wirkt im ersten Moment zwar praktisch, wird aber spätestens dann zur Last, sobald diese Spezifikationsarbeit zur Implementierungszeit nachgeholt werden muss. Damit ist REST schlussendlich eine eher schlechte Schnittstelle für Interoperabilität. Zumal die als REST definierten Schnittstellen oftmals nur das Prädikat REST-like tragen, weil sie die Selbstbeschreibung der Schnittstelle, die HATEOAS bieten könnte, im Kontext betrieblicher Informationssystem gar nicht benötigen. Pull statt Push gilt wiederum auch bei REST.

GraphQL, der erste Lösungsschritt

Eine vielversprechende Alternative zu RPC, SOAP und REST ist das 2015 von Facebook veröffentlichte GraphQL. Wie SQL für relationale Datenbanksysteme ist GraphQL eine Abfragesprache für Daten mit Graphstruktur. Sowohl SQL als auch GraphQL lassen sich als clientorientiert charakterisieren.

Client-directed Queries

GraphQL ist nicht die einzige clientorientierte Abfragesprache. Weitere wichtige Vertreter sind SQL, OData oder SPARQL. Diese Abfragesprachen versetzen Clients in die Lage, Daten angepasster Struktur, Detailgrad und Umfang abzurufen. Sie erfüllen im Gegenzusatz zu GraphQL allerdings nicht sämtliche Anforderungen einer Omnichannel-Echtzeit-Architektur.

Bei GraphQL stellt der Server die Daten nicht in Form einzelner Funktionen oder Ressourcen bereit. Stattdessen definiert das GraphQL-Backend lediglich, welche Daten in Form von Objekten und Feldern es überhaupt gibt, wie sie in Beziehung zueinander stehen und regelt, auf welche der Daten ein Client zugreifen darf. Diese Definition ist das Schema. Dieses muss im Gegensatz zu RPC nicht explizit feingranular implementiert sein.

Einstiegspunkt, Umfang, Detailgrad und Struktur der abgerufenen Daten darf immer der Client auf Grundlage dieses Schemas formulieren. Dazu definiert er in JSON eine Wunschstruktur der Objekte und übergibt sie an den Server. Der Server antwortet schlicht exakt so, wie vom Client gefordert, gibt höchstens Nullwerte zurück, falls der Client auf bestimmte Informationen keinen Zugriff hat. Auch die Antwort des Servers erfolgt als JSON-Struktur. Die Schnittstelle wird auf Serverseite im Idealfall deshalb nur einmal definiert und implementiert, steht unter einem einzigen Endpunkt zur Verfügung und unterstützt dennoch beliebige Data Access Pattern.

Der Preis dafür ist ein geringfügig erhöhter Entwicklungsaufwand je Clientimplementierung, weil die Entwickler die konkrete Abfrage anhand des Schnittstellenkontrakts recherchieren müssen. Im Gegenzug sinkt die Nutzlast beim Datenaustausch auf ein minimales, respektive adäquates, Niveau.

Weitere Vorteile: Der HTTP-Traffic je fachlicher Gesamtabfrage sinkt, weil nur eine Anfrage für die benötigten Daten abgesetzt werden muss. Obendrein sind die Serverentwickler zu einem beliebigen Zeitpunkt während der Entwicklung in der Lage, Ergänzungen an Struktur, Umfang und Detailgrad der Daten vorzunehmen, ohne die Cliententwickler informieren zu müssen. Das vereinfacht die Implementierung und Entwicklung erheblich.

Eine letzte Anforderung der Omnichannel-Echtzeit-Schnittstelle kann aber auch GraphQL nicht vollends abdecken: Push statt Pull. Es gibt zwar mit den Subscriptions ein Event-System, das fügte Facebook aber erst später hinzu und brach dabei mit der etablierten Architektur. Während die Queries und Mutations – Mutation heißen Änderungen an den Daten – über HTTP laufen, laufen die Events hingegen über eine zusätzliche Websocket-Verbindung. Dadurch entstehen zwei Transportwelten.

Weiterhin gehören die Events zwar zum selben Schema, sind aber ein fachlich eigenständiges Konzept neben Queries und Mutations. Der Client kann sich nur vom Server benachrichtigen lassen, dass ein bestimmtes Ereignis eingetreten ist. So gesehen handelt es sich um einen RPC vom Server zum Client. Aber wann der Server den RPC auslöst, ist nicht geregelt. Auch nicht, wie der Client mit diesem Ereignis dann umzugehen hat und ob überhaupt Daten abgerufen werden. Das müssen die Entwickler implementieren. Ein Abonnement eines bestimmten Querys, etwa mit automatischer Übermittlung geänderter Daten, ist deshalb nicht möglich.

GraphQL I/O, der zweite Lösungsschritt

Während GraphQL die formale Sprachspezifikation darstellt, ist GraphQL I/O ein vollständiges, homogen implementiertes Client- und Server-Framework zur Netzwerkkommunikation per GraphQL.

Dazu gleicht GraphQL I/O zuallererst den architektonischen Makel der getrennten Transportwelten aus, indem es sämtliche Kommunikation zwischen Client und Server über Websocket führt. Der ständige Verbindungsauf- und -abbau zum Server entfällt. Die durchaus mehrere Hundert Byte großen HTTP-Header entfallen ebenfalls, weil sie bei der Websocket-Kommunikation irrelevant sind. Der Austausch kleinerer Informationspakete ist damit deutlich effizienter, weil der Protokoll-Overhead im Vergleich zur Nutzlast deutlich sinkt.

Direkter Vergleich: RPC, SOAP, REST, GraphQL und GraphQL I/O

SOAP und REST lassen sich als auf Webtechnologien basierende Varianten von RPC verstehen. GraphQL und GraphQL I/O sind hingegen Abfragesprachen. Der Client legt die gewünschte Datenstruktur, Umfang und Detailgrad fest, der Server löst sie auf.

Außerdem besteht mit Websockets grundsätzlich eine bidirektionale, asynchrone Verbindung zwischen Client und Server. Der Server kann demnach aktiv und selbstständig Daten zum Client übertragen, ohne auf eine vorherige Anfrage des Clients warten zu müssen. Die Verzögerungen zwischen dem Eintritt eines Ereignisses auf Serverseite und der Reaktion auf Clientseite bleibt minimal. Damit lässt sich sowohl die Echtzeitkommunikation realisieren, als auch von Pull zu Push wechseln.

Damit Entwickler die Echtzeitaktualisierung schnell, eingängig und einfach implementieren können, nimmt GraphQL I/O zwei entscheidende Änderungen beim Umgang mit Subscriptions vor. Die erste Änderung: Subscriptions sind nicht von den Queries separiert. Stattdessen lässt sich jeder Query, den ein Client an den Server absetzt, in eine Subscription ändern. Dazu muss lediglich die Callback-Funktion zur Abarbeitung der vom Server erhaltenen Daten an .subscribe() und nicht wie bisher an .then() übergeben werden (siehe Listing). Jedes Mal, wenn der Server künftig Änderungen an den abgerufenen Daten erkennt, informiert er den Client. Der Client führt die Callback-Funktion daraufhin automatisch aus, setzt somit den Query an den Server abermals ab und erhält die aktuellen Daten.

(async () => {
const { Server } = require("graphql-io-server")
const { Client } = require("graphql-io-client")

/*
** ==== SERVER ====
*/

const server = new Server({ url: "http://127.0.0.1:12345" })
server.at("graphql-schema", () => `
type Root {
counter: Counter
}
type Counter {
value: Int
increase: Counter
}
`)
let counter = {
value: 0
}
server.at("graphql-resolver", () => ({
Root: {
counter: (obj, args, ctx, info) => {
ctx.scope.record("Counter", 0, "read", "direct", "one")
return counter
}
},
Counter: {
increase: (obj, args, ctx, info) => {
counter.value++
ctx.scope.record("Counter", 0, "update", "direct", "one")
return counter
}
}
}))
await server.start()

/*
** ==== CLIENT #1 ====
*/

const client1 = new Client({ url: "http://127.0.0.1:12345", debug: 9 })
client1.at("debug", (ev) => console.log(`client1 [${ev.level}] ${ev.msg}`))
await client1.connect()
let subscription = client1.query(`{
counter {
value
}
}`).subscribe((result) => {
console.log("Client #1: Result:", result.data)
})

/*
** ==== CLIENT #2 ====
*/

const client2 = new Client({ url: "http://127.0.0.1:12345", debug: 9 })
client2.at("debug", (ev) => console.log(`client2 [${ev.level}] ${ev.msg}`))
await client2.connect()
setInterval(async () => {
let result = await client2.mutation(`{
counter {
increase { value }
}
}`)
console.log("Client #2: Result:", result.data)
}, 1 * 1000)

})().catch((err) => {
console.log("ERROR", err)
})

(Subscription-Beispiel: Dieses einfache Beispiel startet einen Server sowie zwei Clients, die auf den Server zugreifen. Client 2 ändert die Daten, Client 1 abonniert einen Query und wird stets über die Änderungen informiert.)

Auch auf Serverseite müssen die Entwickler keine Berge versetzen, damit dieses Subscription-System funktioniert. Stattdessen kommt beim Datenzugriff ein Scope Journal zum Einsatz. Dank des Scope Journals kann der Server Änderungen an den Daten erkennen und dann all die Clients benachrichtigen, die eine Subscription auf einen Query registriert haben, der von den geänderten Daten betroffen ist.

Die Aufgabe der Entwickler besteht nur noch darin, dieses Scope Journal bei lesendem und schreibendem Zugriff zu füllen. Dazu fügen sie mit jedem Query und jeder Mutation dem Scope Journal neue Einträge hinzu und vermerken, welche Objekte und Felder betroffen sind. Für normale CRUD-Operationen ist das in den meisten Fällen sogar bereits durch Frameworks weggekapselt. Der Server gleicht nur diese Journale schlussendlich im Hintergrund dann automatisch ab.

Echtzeit-Abstimmung: Die Anwendung Vote implementiert GraphQL I/O. Im Video sind vier Client-Instanzen parallel zu sehen. Sobald in einem Client eine Abstimmung erfolgt, ist der Zwischenstand sofort auf den anderen drei Clients ebenfalls zu sehen.
Angetrieben durch einen automatisierten, künstlich gedrosselten Bedientest nehmen 20 Clients an einer Abstimmung teil. Der 21. Client zeigt den aktuellen Zwischenstand der Abstimmung in Echtzeit, ebenso wie die 20 anderen.

Fazit

GraphQL und GraphQL I/O sind Umsetzungen, die ständig variierende Data Access Pattern unterstützt, sich jederzeit erweitern lässt und eine stets adäquate Datenkommunikation sicherstellt. Gleichzeitig kommuniziert es Datenänderungen über alle Kanäle und Clients hinweg in Echtzeit, ohne Entwickler und Architekten mit Komplexität bei der Implementierung zu überfordern. Einer Omnichannel-Echtzeit-Strategie steht somit nichts mehr im Weg.

Das heißt aber nicht, dass GraphQL für alles reicht. Authentifizierung und Autorisierung sind etwa explizit über ein eigenes REST-Backend gelöst. BLOBs, wie sie im Rahmen von Dokumentdatenbanken oder beim Streaming vorkommen, sollten nicht per GraphQL übertragen werden. Denn sie würden mit Base64 enkodiert in die JSON-Struktur verpackt und dadurch gut 30 Prozent an Umfang gewinnen. Auch GraphQL zur Introspektion des Schemas hat einen eigenen REST-Endpunkt. (bbo)

Mark Lubkowitz
ist Software Engineer und Journalist bei msg. Seine Hauptthemen sind Web Technologies und Digitale Transformation.

Onlinequellen

  • GraphQL Die Abfragesprache GraphQL wurde von Facebook entwickelt und wird mittlerweile auch von Github eingesetzt.
  • GraphQL I/O GraphQL I/O implementiert einerseits GraphQL auf Client- und Server-Seite, andererseits fügt es Echtzeit-Kommunikation hinzu.
  • Representational State Transfer: Ausgangsbasis für REST ist die Dissertation Roy Fieldings.
  • Units and Persons: Ein Implementierungsbeispiel, unter anderem für GraphQL I/O, bietet das quelleoffene Units and Persons.