Blog

🤔 Sollte GraphQL für verschiedene Nutzer unterschiedlich sein?

Leonardo Losoviz
Von Leonardo Losoviz ·

GraphQL ist eine Schnittstelle zum Abrufen von Daten aus einer Quelle, wobei die GraphQL-Spec die Anforderungen an die Schnittstelle definiert. Solange diese Anforderungen erfüllt sind, ist es GraphQL egal, wie das erreicht wird. Der GraphQL-Server kann also in JavaScript mit Promises implementiert werden, mit einer auf Golang basierenden parallelen Architektur, gemappt auf eine Excel-Datei, oder was auch immer – all das können gültige Implementierungen der GraphQL-Spec sein.

GraphQL steht zwischen dem Client und den Backend-Diensten

Wie die Engine des Servers implementiert ist, spielt für die erfolgreiche Ausführung einer GraphQL-Anfrage keine Rolle, da die Interaktion zwischen Client und Server immer gleich ist: Sie erfolgt durch das Absenden einer GraphQL-Query mit einer definierten Syntax und das Erhalten einer entsprechenden Antwort im JSON-Format.

Wenn ich sage, dass die Implementierung nicht wichtig ist, meine ich das aus der Perspektive des API-Nutzers, der einfach Daten vom Server abrufen möchte. Wie die zurückgegebenen Daten produziert wurden, interessiert ihn nicht.

Die Situation ändert sich jedoch für den serverseitigen Entwickler, der an der API arbeitet, für den die Details der Implementierung sehr wichtig sind. Wenn ich meine GraphQL-API in PHP programmiere, tue ich mein Bestes, damit meine API so effizient wie möglich aufgelöst wird und ein so elegantes Architekturdesign wie möglich hat – und dabei die von PHP gebotenen Möglichkeiten nutze.

PHP vs Java vs JavaScript

Es gibt also einen möglichen Interessenkonflikt zwischen der Notwendigkeit, die API zu schützen, und den erwarteten Fähigkeiten von Entwicklern, die an der API arbeiten und nicht wollen, dass ihnen Funktionen entzogen werden, die von der zugrunde liegenden Sprache unterstützt werden (wie die Möglichkeit, rekursiven Code auszuführen).

Dieser Konflikt wurde in Issue #929: Allow recursive references in fragments deutlich, die argumentiert, dass GraphQL Rekursionen in Fragmenten nicht verbieten sollte.

Bei einem vergangenen Meetup der GraphQL Working Group erklärte Roman, der Entwickler, der die Issue aufgeworfen hat, warum er mit der von der Spec auferlegten Einschränkung nicht einverstanden ist:

Ich bin ein serverseitiger Entwickler und habe das Gefühl, dass die Spec zu sehr über die serverseitige Ausführung spricht, während sie sich auf das konzentrieren sollte, was der Client geliefert bekommen möchte – nicht wie

Die Regel, die Rekursionen in Fragmenten verbietet, wurde mit dem Ziel begründet, die öffentliche API sicher zu halten. Schließlich wurde GraphQL von Facebook entwickelt, um Daten an deren öffentlich zugängliche Anwendung zu liefern, und Nutzer sollten keine Schwachstelle im API-Design ausnutzen können, die den Dienst zum Absturz bringen könnte.

GraphQL-Schöpfer Lee Byron äußerte drei wesentliche Bedenken:

unendliche Rekursion; Einschränkungen wären nicht nur eine Spezifikation – wie und wann sollte sie anhalten

Datenvalidierung; denselben Wert mehrfach zurückzugeben – wie wird das in den Daten dargestellt. Idealerweise möchtest du erkennen, dass es zyklisch ist, und sofort anhalten, aber einige Server können das nicht erkennen und können viele Male durchlaufen, bevor sie bemerken, dass etwas schiefgelaufen ist, und anhalten

was kostet es, das nicht zu haben; rechtfertigt das diese Probleme? Nein; es ist immer möglich, die Anzahl der Ebenen in deiner Query anzugeben – das ist de facto die "desugared" Version dessen, was wir tun würden, wenn wir das in GraphQL behandeln würden

Aus ihrer jeweiligen Perspektive haben sowohl Roman als auch Lee recht. Lee Byron ist besorgt über die Sicherheit der öffentlichen GraphQL-API. Das Vermeiden rekursiver Fragmente ist gerechtfertigt, um sicherzustellen, dass keine böswilligen Akteure das System durch die Ausführung einer nie endenden Schleife in der Query zum Absturz bringen können, und um sogar das Risiko eines teaminternen "Self-DDoSing" zu beseitigen, das passieren könnte, wenn versehentlich eine Query veröffentlicht wird, die das System blockiert.

Roman hingegen ist besorgt über die Einschränkungen seiner eigenen Fähigkeiten beim Erstellen einer GraphQL-API. Da Roman möglicherweise der einzige Nutzer seiner API ist (d.h. eine private API, die nicht für Nutzer freigegeben ist), oder weil sein Server in der Lage sein kann, wiederkehrende Zyklen zu erkennen und zu stoppen, hält er die GraphQL-Einschränkung für schädlich und nicht gerechtfertigt.

Im Kern der Diskussion geht es nicht darum, ob rekursive Fragmente erlaubt sein sollten oder nicht, sondern um etwas Grundlegenderes: Wer ist GraphQLs Zielgruppe? Wenn es keine einzelne Gruppe ist, kann eine einzelne API-Spezifikation die Anforderungen aller verschiedenen Stakeholder erfüllen? Und wenn der Konflikt nicht vermieden werden kann, kann er zumindest irgendwie abgemildert werden?

Lass uns diese Fragen erkunden.

Wer ist GraphQLs Zielgruppe?

GraphQL wird von verschiedenen Arten von Stakeholdern genutzt, unter denen wir identifizieren können:

1. API-Nutzer: Diejenigen, die Daten von einem GraphQL-Endpoint abrufen, aus welchem Grund auch immer. Zum Beispiel können wir alle Nutzer von GitHubs öffentlicher GraphQL-API sein, um Daten zu unseren GitHub-Repos abzurufen.

2. Clientseitige Entwickler: Diejenigen, die clientseitige Anwendungen erstellen, die von einem GraphQL-Endpoint betrieben werden. Zum Beispiel verlassen sich Entwickler, die Websites mit Gatsby bauen, auf GraphQL, um den Inhalt der Website abzurufen.

3. Backend-Entwickler: Diejenigen, die die Resolver für die GraphQL-API erstellen.

Zusätzlich müssen wir beachten, dass die GraphQL-API öffentlich oder privat sein kann:

Öffentliche API: Da jeder Zugang zum GraphQL-Endpoint hat, müssen wir uns um Sicherheitsmaßnahmen kümmern, um Angriffe durch böswillige Akteure zu vermeiden.

Private API: Da nur vorgesehene Akteure Zugang zur API erhalten, gibt es keine inhärenten Sicherheitsrisiken, und Self-DDoSing kann mit guten Programmierpraktiken leicht vermieden werden.

Erfüllt eine einzige API-Spezifikation die Anforderungen aller Stakeholder?

Die von Roman aufgeworfene Issue lässt sich so interpretieren: "Wenn meine GraphQL-API privat ist und ich genau weiß, was ich tue (mit 100%iger Gewissheit, dass mein Code wie erwartet funktionieren wird und keine blockierenden Ausführungen produziert werden), warum kann ich dann keine Rekursionen in Fragmenten verwenden?"

Rekursionen @ xkcd

Ein Beispiel für diese Situation tritt immer dann auf, wenn wir ein von GraphQL betriebenes Framework zum Erstellen statischer Websites verwenden (wie Gatsby, Next.js oder RedwoodJS), da die GraphQL-API oft privat sein wird und wir unsere Anwendung nicht versehentlich mit DDoS angreifen und negative Konsequenzen erleiden können (schlimmstenfalls stürzt sie beim Erstellen der statischen Website in einer Entwicklungs- oder Staging-Umgebung ab).

Entwickler, die das oben genannte Setup verwenden, könnten sich durchaus fragen, warum die GraphQL-Spec ihnen verbietet, nützliche Funktionen zu verwenden, die für ihr Setup keinerlei negative Konsequenzen haben.

Zusammenfassend lässt sich sagen: Indem die GraphQL-Spec rekursive Fragmente verbietet, verhängt sie eine Sicherheitsmaßnahme, die nur für eine Auswahl aller möglichen Verwendungen von GraphQL gilt, nicht für alle – um auf der sicheren Seite zu sein.

Könnte die GraphQL-Spec alle Stakeholder besser zufriedenstellen?

Wenn verschiedene Stakeholder unterschiedliche Anforderungen haben, wie kann die GraphQL-Spec alle zufriedenstellen? (Die Idee ist, die Spec nicht zu forken und angepasste Versionen für bestimmte Zielgruppen zu produzieren.)

Lass uns ein paar Ideen erkunden, wobei die erste den Spec-Beitragsprozess durchlaufen müsste, während die zweite das nicht würde.

Feature-Toggle auf Ebene der GraphQL-Spec

Ein möglicher Weg ist, dass die Spec Regeln "vorschlägt" aber nicht "auferlegt". In diesem Fall könnte die Regel, die Rekursionen in Fragmenten verbietet, dringend empfohlen werden, aber die Funktion wäre dennoch akzeptiert.

Diese Lösung würde die Standardbedingung rekursiver Fragmente von "obligatorisch" auf "optional" ändern, was zwei negative Konsequenzen hätte:

  • Die API wäre standardmäßig unsicher (das Szenario, das Lee Byron vermeiden möchte)
  • Es würde einen Breaking Change produzieren, da eine verbotene Query dann erlaubt wäre

Dann wäre es besser, die Option umzukehren, indem Rekursionen in Fragmenten standardmäßig weiterhin verboten bleiben, aber die Möglichkeit gegeben wird, einen Feature-Flag zu aktivieren, der dieses Verhalten deaktiviert. Da die Funktion explizit deaktiviert werden muss, wird das nur von Administratoren durchgeführt, die wissen, was sie tun.

Da die Funktion unter bestimmten Setups am wertvollsten ist, könnten GraphQL-Server und Frameworks entscheiden, ob/wie/wann sie die Konfiguration anbieten. Zum Beispiel könnte Gatsby die Option beim Erstellen statischer Websites prominent über eine Benutzeroberfläche anzeigen und sie andernfalls ausblenden.

Die allgemeine Idee ist, dass die GraphQL-Spec "aktivierte, aber optionale Funktionen" unterstützt, die über die Konfiguration aktiviert/deaktiviert werden können, und deren Standardzustand derjenige ist, den sie bereits in der Spec haben.

Das Verbieten rekursiver Fragmente wäre eine davon, und es könnte auch andere solche Funktionen geben, wie einen Map-Typ, der von Lee Byron nicht für die Spec akzeptiert wurde weil:

Es gibt erhebliche Kompromisse zwischen einem Map-Typ und einer Liste von Schlüssel/Wert-Paaren. Ein Problem ist die Paginierung über die Sammlung. Listen von Werten können klare Paginierungsregeln haben, während Maps, die oft ungeordnete Schlüssel-Wert-Paare haben, viel schwieriger zu paginieren sind.

Ein weiteres Problem ist die Verwendung. Meistens wird Map in APIs verwendet, wo ein Feld des Wertes indiziert wird, was meiner Meinung nach ein API-Anti-Pattern ist, da Indizierung ein Speicherungsproblem und ein Client-Caching-Problem ist, aber kein Transportproblem. Dieses Anti-Pattern bereitet mir Sorgen. Obwohl es einige gute Verwendungsmöglichkeiten für Maps in APIs gibt, befürchte ich, dass die häufige Verwendung für diese Anti-Patterns sein wird, also empfehle ich, mit Vorsicht vorzugehen.

Lee Byron äußerte seine Befürchtung, dass die Funktion als Anti-Pattern verwendet werden würde. Er erkannte jedoch auch an, dass es gute Verwendungsmöglichkeiten dafür gibt. Da die Issue viel Unterstützung aus der Community erhielt (mit über 150 👍), könnten Entwicklern die Möglichkeit gegeben werden, das Hinzufügen eines Map-Typs zu ihren Schemas explizit zu aktivieren und mit den Konsequenzen umzugehen.

Feature-Toggle durch GraphQL-Server

Wenn der obige Vorschlag keine Unterstützung findet, weil er für die GraphQL-Spec zu riskant ist, ist eine Alternative, ihn auf Ebene des GraphQL-Servers zu implementieren. GraphQL-Server könnten dann eine benutzerdefinierte Funktion bereitstellen, die Rekursionen in Fragmenten deaktiviert.

Verallgemeinert man die Idee, könnten GraphQL-Server anbieten, bestimmte Funktionen der Spec zu deaktivieren und andere zu aktivieren, die in der Spec fehlen. Damit dieses Verhalten keine Überraschungen produziert, müssen die Server sicherstellen, dass der Standardzustand derjenige ist, der von der Spec gefordert wird, und der Administrator der API muss sich der Konsequenzen der Aktivierung der Funktion vollständig bewusst sein. (Dies ist die Strategie, die Gato GraphQL für seine "innovative features" verfolgt.)

Fazit

Da GraphQL immer populärer geworden ist, haben neue Frameworks, die neue Fähigkeiten unterstützen, es zu einem Teil ihres Stacks gemacht, und neue Stakeholder (und neue Arten davon) wurden einbezogen. Eine Spezifikation, die ursprünglich von Facebook erstellt wurde, um zu definieren, wie seine Anwendungen Daten von seinen Servern abrufen würden, muss sich zunehmend mit mehr Anwendungsfällen auseinandersetzen.

Es ist unvermeidlich, dass Konflikte entstehen, bei denen eine Gruppe von Stakeholdern eine Funktion benötigt, die für andere Stakeholder kontraproduktiv oder sogar schädlich ist, wie es bei rekursiven Fragmenten der Fall ist. Was kann getan werden, um die Situation zu verbessern und zu verhindern, dass unzufriedene Stakeholder von GraphQL enttäuscht werden?

Ich habe argumentiert, dass die Spec die Möglichkeit bieten könnte, eine Funktion zu "deaktivieren", sodass Administratoren, die wissen, was sie tun, einige Einschränkungen entfernen können, um ihre eigenen Anforderungen zu erfüllen. Ich selbst bin nicht mit dieser Lösung einverstanden, bringe sie aber trotzdem auf den Tisch, weil diese Diskussion geführt werden muss. Da diese Idee umstritten ist, ist eine bessere Alternative, dass GraphQL-Server dieses Verhalten über benutzerdefinierte Funktionen bereitstellen, die explizit aktiviert werden müssen.


Abonniere unseren Newsletter

Bleib über alle Updates zu Gato GraphQL auf dem Laufenden.