Architektur
ArchitekturCode-first GraphQL-Server

Code-first GraphQL-Server

Das GraphQL-Schema definiert die Verträge eines GraphQL-Dienstes, indem es die Menge der Typen, Felder und Mutations bereitstellt, die gegen den Dienst ausgeführt werden können. Beim Erstellen eines GraphQL-Dienstes können wir uns entscheiden für:

  • das Schema als einzige Quelle der Wahrheit zu nutzen und unseren gesamten Implementierungscode an dessen Definitionen anzupassen
  • unseren Code als einzige Quelle der Wahrheit zu nutzen und das Schema als ein daraus generiertes Artefakt zu behandeln

In beiden Fällen erhalten wir einen voll funktionsfähigen GraphQL-Dienst, aber je nach gewähltem Ansatz können wir mehr oder weniger Features einfacher oder schwieriger umsetzen. Diese beiden Ansätze werden jeweils als „schema-first" (besser bekannt als „SDL-first") und „code-first" bezeichnet.

Gato GraphQL verwendet den Code-first-Ansatz. Schauen wir uns an, warum das so ist.

Warum Gato GraphQL code-first verwendet

Beim Code-first-Ansatz beginnen wir mit der Implementierung der Resolver und generieren dann, mit dem Code als einziger Quelle der Wahrheit, das Schema als Artefakt. Das Schema wird also durch Ausführen eines Skripts erstellt, anstatt wie bei SDL-first manuell erstellt zu werden. Da code-first ebenfalls ein Schema besitzt, fehlt nichts Wesentliches im Vergleich zu SDL-first.

Allerdings bietet code-first einen bedeutenden Vorteil gegenüber SDL-first: die Möglichkeit, dynamische Schemas bereitzustellen, die je nach Kontext ihre Struktur und Attribute ändern können und zur Laufzeit über Code gesteuert werden. Tatsächlich sind alle großartigen Features von Gato GraphQL eine direkte Folge der Entscheidung für code-first.

Vorteile von code-first

Ein dynamisches Schema bietet unter anderem alle folgenden Vorteile:

Die Quelle der Wahrheit für das Schema ist eine Obermenge der von GraphQL benötigten. Die zusätzlichen Eigenschaften (wie globale Felder, globale Verbindungen, globale Direktiven und persistierte Fragmente) können bereits in unserer API genutzt werden, ohne darauf warten zu müssen, dass sie zur GraphQL-Spezifikation hinzugefügt werden – falls das überhaupt je passiert.

Da die Quelle der Wahrheit nicht an das Schema gebunden ist, können wir auch für beliebige andere Systeme ein Schema generieren: GraphQL ist nur eines der möglichen Ziele. Zum Beispiel kann aus derselben Quelle der Wahrheit ein JSON-Schema für einen REST-Dienst generiert werden.

Die API kann gleichzeitig öffentlich/privat sein, je nachdem ob der Benutzer eingeloggt ist oder nicht und welche Rollen der eingeloggte Benutzer hat, oder sie kann mehr oder weniger Felder basierend auf einer anderen Eigenschaft anbieten, z. B. ob der Benutzer die PRO-Mitgliedschaft bezahlt hat.

Typen wissen nicht im Voraus, welche Felder sie auflösen werden. Stattdessen binden sich Feld-Resolver über das Publish-Subscribe-Muster an Typ-Resolver, und Feld-Resolver können andere Feld-Resolver überschreiben. Diese Eigenschaft macht die API sehr erweiterbar und ermöglicht es uns, einen allgemeinen Code für unsere API zu haben und ihn auf Anwendungsebene für einen bestimmten Client oder ein bestimmtes Projekt anzupassen.

Ein Feld kann nicht nur von einem, sondern von vielen Feld-Resolvern verarbeitet werden: Jeder Feld-Resolver in der Kette kann zur Laufzeit entscheiden, ob er das Feld basierend auf einer Eigenschaft verarbeitet oder es entlang der Kette weitergibt. Zum Beispiel kann ein spezieller Feld-Resolver nur dann verwendet werden, wenn ein Feldargument "source: testing" übergeben wird, sodass er auf einigen Produktionsseiten getestet werden kann, bevor er allgemein veröffentlicht wird; dieselbe Strategie ermöglicht es auch, schnelle Bugfixes für einen bestimmten Client oder eine bestimmte Umgebung bereitzustellen, ohne das Risiko unbeabsichtigter Nebeneffekte an anderer Stelle einzugehen.

Typen und Interfaces können automatisch in Namespaces eingeteilt werden, um Kollisionen mit Drittanbietern zu vermeiden.