GraphQL-Codegen: Warum es sich lohnt
Michel
GraphQL ist eine tolle Sache! Eine getypte API, Vermeidung von Overfetching und Generierung von Frontend-Services mit
nur einem Befehl. Kein Wunder also, dass auch wir bei ATMINA auf GraphQL bauen; umgesetzt
mit HotChocolate im .NET Core Backend, GraphQL Flutter in
mobilen Projekten und Apollo Angular in unseren Webanwendungen. Mit
dem GraphQL Code Generator und verfügbaren Plugins haben wir in
der Vergangenheit bereits Apollo Queries automatisch generiert. Zunächst alles in einer Datei, später dann mit Hilfe
des near-operation-file
Preset direkt bei den zugehörigen GraphQL Operations.
Das Problem
Das Zusammenspiel zwischen den einzelnen Komponenten funktioniert wunderbar, insbesondere der Code Generator nimmt viel Arbeit ab! Allerdings gab es doch Kritik an dem erzeugten Output:
- Scalars werden durch einen Scalars-Wrapper definiert statt direkt TypeScript Types wie
string
odernumber
zu nutzen
Der Output nutzt viele Utility Types wiePick
undMaybe
, was die Lesbarkeit in Fehlermeldungen massiv verschlechtert - Alle Typen (Scalars, Enums, Input- und Object-Types) werden in eine
types.ts
Datei generiert, aber oftmals nicht genutzt. Wenn man aber nur einen Enum importieren möchte, kann es schnell passieren, dass auch andere Typen anstelle des eigentlichen Query-Ergebnisses aus der Datei mit importiert werden und so Attribute als verfügbar angezeigt werden, die es nicht sind - Es gibt keine Möglichkeit für Aliases für Selections (außer der fragwürdigen Nutzung von GraphQL Fragments), wodurch Code-Wiederverwendbarkeit drastisch reduziert wird
Zum Vergleich: der in der folgenden Fehlermeldung angezeigte Type ist aufgrund der Verwendung von Pick
sowie des
Intersection-Operators (&
) so komplex, dass Teile davon im Tooltip des Editors ausgelassen und durch Ellipsen ersetzt
werden. Darunter zeigen wir eine vereinfachte Form des Types, der auch vollständig im Tooltip angezeigt werden könnte.
import {ShelvesQuery} from './example.generated';
const query: ShelvesQuery = {
shelves: [
{
items: [
{
// Fehlerquelle: hier fehlt `id`!
title: 'The Hobbit',
author: {
id: '',
name: 'J. R. R. Tolkien',
},
},
],
},
],
};
Fehlermeldung in der Standardkonfiguration des Plugins
Gewünschte Fehlermeldung nach Vereinfachung des Types
Umsetzung
Im Rahmen eines unserer OKR-Zyklen sind wir dieses
Problem angegangen und haben auf der Grundlage der existierenden GraphQL-Codegen
Plugins typescript
und typescript-operations
unsere eigenen Plugins geschrieben, die (wenn zusammen genutzt) die
oben beschriebenen Dinge verbessert. Die folgenden Beispiele basieren
auf diesem Schema.
@atmina/only-enum-types
@atmina/only-enum-types
ist eine reduzierte Variante des typescript Plugins für GraphQL-Codegen, das ausschließlich
Enums in der angemerkten types Datei ablegt. Mit Hilfe der Option onlyOperationTypes: true
im typescript Plugin lässt
sich der Umfang der generierten Typen zwar bereits auf Enums, Scalars und Input-Typen reduzieren; eine Option zum
Generieren ausschließlich von Enums ist allerdings nicht vorhanden. Durch das Custom-Plugin wird das Risiko von falschen
(oder zu vielen) Imports und die Größe der Datei deutlich verkleinert.
Vorher
Nachher
@atmina/local-typescript-operations
@atmina/local-typescript-operations
ist ein Ersatz für das typescript-operations
Plugin für GraphQL-Codegen, das
einen Großteil der Kritik umsetzt, die bei uns am “Original” geübt wurde und hat in der Entwicklung den meisten Aufwand
gekostet. Auch typescript-operations
hat Konfigurationsoptionen, so konnte beispielsweise die
Anforderung, Pick
durch vollständige Typen zu ersetzen, durch preResolveTypes: true
umgesetzt werden.
typescript-operations
nutzt für die Erzeugung der Operation-Typen sowohl Input- als auch (in bestimmten
Konfigurationen) die Object-Types, die sich eigentlich durch das typescript-Plugin in der großen types.ts
Datei
befinden. Entsprechend mussten nun die benötigten Input-Types ermittelt und (rekursiv) generiert werden. Verweise auf
die zuvor importierten Typen mussten (mit der Ausnahme von Enums) korrigiert werden und die Nutzung des vom Plugin
definierten Utility Types Maybe<T> = T | null
wurde durch direkte Verwendung von T | null
ersetzt.
Vorher
Nachher
Und dann war da noch die Sache mit den Aliases für Selections. In einem zuvor erstellen Konzept wurde die Idee einer
GraphQL Directive ins Spiel gebracht, mit der in Queries und Mutations Selectionsets markiert werden können, um für
diese einen Typen zu erzeugen und so nicht auf Fragments angewiesen zu sein. In der Implementierung wurde der im Konzept
festgelegte Name @export
verwendet und nach guten 40 Stunden war auch dieses Plugin bereit zur Nutzung!
Die folgenden Queries produzieren (abgesehen vom Namen) identische Typen, der rechte Weg erlaubt allerdings auch die Nutzung von Query Variablen für Fields, was mit Fragments nicht möglich ist:
query ExampleQuery {
shelves {
floor
items {
...Book
}
}
}
fragment Book on Book {
id
author {
id
name
}
}
query ExampleQuery {
shelves {
floor
items @export(exportName: "Book") {
id
... on Book {
author {
id
name
}
}
}
}
}
Die Zukunft des Plugins
Mit der Implementierung der beiden Plugins wurden die Probleme, die bei der Nutzung der GraphQL Code-Generator
Plugins typescript
und typescript-operation
aufgetreten sind, für uns behoben. Zunächst waren die beiden Plugins nur
durch die interne GitLab NPM-Registry bei uns verfügbar. Da ATMINA in der Entwicklung viel auf Open Source Software
setzt, möchten wir nun der Community etwas zurückgeben und wir haben uns dazu entschieden, die beiden Plugins
auf GitHub und NPM
(@atmina/only-enum-types
, @atmina/local-typescript-operations
)
zu veröffentlichen. Wir hoffen, dass anderen mit ähnlichen Gedanken zu generiertem TypeScript GraphQL Code damit
geholfen wird, zumal die Plugins auch unabhängig von der tatsächlichen Service-Implementierung sind; das heißt, dass die
ATMINA Plugins mit GraphQL Codegen für Apollo Angular, urql und weiteren funktionieren werden.
Fazit
Mit der Entwicklung der Plugins wurde bei ATMINA nicht nur auf interne Kritik an genutzten Tools reagiert, wir haben auch den Grundstein für eine rege Teilnahme in der GraphQL Community gelegt. Mit den Plugins erwarten wir nicht nur effizientere Arbeit bei uns, wir gehen auch einen weiteren Schritt auf dem Weg zu mehr Präsenz in der Entwickler-Community und lernen dabei eine Menge dazu. Die Zeit, die im Rahmen von OKR in das Projekt gesteckt wurde, hat sich für uns auf jeden Fall gelohnt und wir hoffen, dass es auch Dir weiterhilft!
Themen:
- Softwareentwicklung
- Programmiersprachen
- Von Entwickler zu Entwickler