🤔 GraphQL dovrebbe essere diverso per utenti diversi?
GraphQL è un'interfaccia per recuperare dati da un'origine, dove la spec GraphQL definisce i requisiti per l'interfaccia. Finché questi requisiti sono soddisfatti, a GraphQL non importa come ci si arrivi. Il server GraphQL può quindi essere implementato in JavaScript usando le promise, usando un'architettura concorrente basata su Golang, mappato a un file Excel, o quant'altro, e tutte queste possono essere implementazioni valide della spec GraphQL.

Il modo in cui è implementato il motore del server non è importante per la corretta esecuzione di una richiesta GraphQL, poiché l'interazione tra client e server è sempre la stessa: avviene inviando una query GraphQL usando una sintassi definita e ottenendo una risposta corrispondente in formato JSON.
Ora, quando dico che l'implementazione non è importante, lo intendo dalla prospettiva dell'utente dell'API, che intende semplicemente ottenere dati dal server. Come sono stati prodotti i dati restituiti non ha alcun interesse.
Ma la situazione cambia per lo sviluppatore lato server che lavora sull'API, per cui i dettagli dell'implementazione sono in effetti molto importanti. Se programmo la mia API GraphQL in PHP, farò del mio meglio affinché la mia API venga risolta nel modo più efficiente possibile e abbia un design architetturale il più elegante possibile, usando le capacità offerte da PHP.

Abbiamo quindi un possibile conflitto di interessi tra la necessità di proteggere l'API e le capacità attese dagli sviluppatori che lavorano sull'API, i quali non vogliono che vengano loro tolte funzionalità supportate dal linguaggio sottostante (come la possibilità di eseguire codice ricorsivo).
Questo conflitto è diventato evidente nella issue #929: Allow recursive references in fragments, che sostiene che GraphQL non dovrebbe vietare le ricorsioni nei fragment.
In un meetup passato del gruppo di lavoro GraphQL, Roman, lo sviluppatore che ha sollevato la issue, ha espresso il motivo per cui è in disaccordo con la limitazione imposta dalla spec:
Sono uno sviluppatore lato server e ho l'impressione che la spec parli troppo dell'esecuzione lato server, mentre dovrebbe concentrarsi su ciò che il client vuole ricevere — non sul come
La regola che vieta le ricorsioni nei fragment è stata giustificata sulla premessa di mantenere l'API pubblica sicura. Dopotutto, GraphQL è stato creato da Facebook per fornire dati alla sua applicazione rivolta al pubblico, e gli utenti non dovrebbero essere in grado di sfruttare una falla nel design dell'API che potrebbe far cadere il servizio.
Il creatore di GraphQL, Lee Byron, ha espresso tre preoccupazioni principali:
ricorsione infinita; le limitazioni non sarebbero solo una specifica — come e quando dovrebbe fermarsi
validazione dei dati; restituire più volte lo stesso valore, come viene rappresentato nei dati. Idealmente vuoi rilevare che è ciclico e fermarti immediatamente, ma alcuni server non riescono a rilevarlo e possono ripetere il ciclo molte volte prima di accorgersi che qualcosa è andato storto e fermarsi
qual è il costo di non avere questo; lo giustifica questi problemi? No; è sempre possibile specificare il numero di livelli di profondità nella tua query — questa è di fatto la versione "desugared" di ciò che faremmo se dovessimo gestire questo in GraphQL
Partendo dalle proprie prospettive, sia Roman che Lee hanno ragione. Lee Byron è preoccupato per la sicurezza dell'API GraphQL pubblica. Evitare i fragment ricorsivi è giustificato per assicurarsi che nessun attore malintenzionato possa far cadere il sistema eseguendo un ciclo ricorsivo senza fine nella query, ed eliminare persino il rischio di un "self-DDoSing" di squadra, che potrebbe verificarsi se viene pubblicata involontariamente una query che blocca il sistema.
Roman, invece, è preoccupato per le limitazioni alle proprie capacità di creare un'API GraphQL. Poiché Roman può essere l'unico consumatore della sua API (cioè un'API privata che non è esposta agli utenti), o perché il suo server può avere la capacità di rilevare e fermare i cicli ricorsivi, ritiene che la limitazione di GraphQL sia dannosa e ingiustificabile.
Al centro della discussione, il problema non è se i fragment ricorsivi debbano essere consentiti o meno, ma qualcosa di più fondamentale: chi è il destinatario di GraphQL? Se non è un singolo gruppo, una singola specifica di API potrebbe soddisfare i requisiti di tutti i diversi stakeholder? E se il conflitto non può essere evitato, può almeno essere in qualche modo mitigato?
Esploriamo queste domande.
Chi è il destinatario di GraphQL?
GraphQL viene usato da diversi tipi di stakeholder, tra cui possiamo identificare:
1. Utenti dell'API: Coloro che consumano dati da un endpoint GraphQL, per qualsiasi motivo. Per esempio, possiamo essere tutti utenti dell'API GraphQL pubblica di GitHub, per recuperare dati relativi ai nostri repository GitHub.
2. Sviluppatori lato client: Coloro che creano applicazioni lato client alimentate da un endpoint GraphQL. Per esempio, gli sviluppatori che costruiscono siti con Gatsby si affidano a GraphQL per recuperare il contenuto del sito.
3. Sviluppatori backend: Coloro che creano i resolver per l'API GraphQL.
Inoltre, dobbiamo notare che l'API GraphQL può essere pubblica o privata:
API pubblica: Poiché chiunque ha accesso all'endpoint GraphQL, dobbiamo preoccuparci delle misure di sicurezza per evitare attacchi da parte di attori malintenzionati.
API privata: Poiché solo gli attori previsti ottengono l'accesso all'API, non ci sono rischi di sicurezza intrinseci, e il self-DDoSing può essere facilmente evitato con buone pratiche di codifica.
Una singola specifica di API soddisfa i requisiti di tutti gli stakeholder?
La issue sollevata da Roman può essere interpretata così: "Se la mia API GraphQL è privata, e so esattamente cosa sto facendo (avendo la certezza al 100% che il mio codice funzionerà come previsto e che non verranno prodotte esecuzioni bloccanti), allora perché non posso usare le ricorsioni nei fragment?"

Un esempio di questa situazione si verifica ogni volta che usiamo un framework alimentato da GraphQL per costruire siti statici (come Gatsby, Next.js o RedwoodJS), perché l'API GraphQL sarà spesso privata, e non possiamo inavvertitamente fare un DDoS della nostra applicazione e subire conseguenze negative (nel peggiore dei casi andrà in crash durante la costruzione del sito statico in un ambiente di sviluppo o di staging).
Gli sviluppatori che usano la configurazione di cui sopra potrebbero benissimo chiedersi perché la spec GraphQL vieta loro di usare funzionalità benefiche, che non hanno alcuna conseguenza negativa per la loro configurazione.
In conclusione, vietando i fragment ricorsivi, la spec GraphQL impone una misura di sicurezza che si applica a una selezione di tutti gli usi potenziali di GraphQL, non a tutti, per essere prudente.
La spec GraphQL potrebbe soddisfare meglio tutti gli stakeholder?
Se diversi stakeholder hanno requisiti diversi, come può la spec GraphQL soddisfarli tutti? (L'idea è di evitare di forkare la spec e produrre versioni personalizzate per destinatari specifici.)
Esploriamo un paio di idee, dove la prima dovrebbe passare attraverso il processo di contribuzione alla spec, mentre la seconda no.
Feature-toggle a livello della spec GraphQL
Una possibile strada da percorrere è far sì che la spec "suggerisca" ma non "imponga" le regole. In questo caso, la regola che vieta le ricorsioni nei fragment potrebbe essere fortemente suggerita, ma la funzionalità sarebbe comunque accettata.
Ora, questa soluzione cambierebbe la condizione predefinita dei fragment ricorsivi da "obbligatorio" a "opzionale", il che produrrebbe due conseguenze negative:
- L'API sarebbe non sicura per impostazione predefinita (lo scenario che Lee Byron vuole evitare)
- Produrrebbe un breaking change, poiché una query vietata sarebbe poi consentita
Sarebbe quindi preferibile capovolgere l'opzione, mantenendo le ricorsioni nei fragment ancora vietate per impostazione predefinita ma dando la possibilità di attivare un feature-flag che disabilita questo comportamento. Poiché la funzionalità deve essere esplicitamente disabilitata, sarà fatto solo dagli amministratori che sanno cosa stanno facendo.
Poiché la funzionalità è più preziosa in determinate configurazioni, i server e i framework GraphQL potrebbero decidere se/come/quando offrire la configurazione. Per esempio, Gatsby potrebbe mostrare in modo evidente l'opzione tramite un'interfaccia durante la creazione di siti statici, e nasconderla altrimenti.
L'idea generale è che la spec GraphQL supporti "funzionalità abilitate ma opzionali", che possono essere abilitate/disabilitate tramite configurazione, e il cui stato predefinito è quello che hanno già nella spec.
Vietare i fragment ricorsivi ne sarebbe una, e potrebbero esserci anche altre funzionalità del genere, come un tipo Map, che non è stato accettato nella spec da Lee Byron perché:
Ci sono compromessi significativi tra un tipo Map e una lista di coppie chiave/valore. Un problema è la paginazione sulla collezione. Le liste di valori possono avere regole di paginazione chiare, mentre le Map, che spesso hanno coppie chiave-valore non ordinate, sono molto più difficili da paginare.
Un altro problema è l'utilizzo. Molto spesso Map è usato all'interno delle API dove un campo del valore viene indicizzato, il che a mio parere è un anti-pattern di API perché l'indicizzazione è un problema di archiviazione e un problema di caching lato client ma non un problema di trasporto. Questo anti-pattern mi preoccupa. Sebbene ci siano alcuni buoni utilizzi per Map nelle API, temo che l'utilizzo comune sarà per questi anti-pattern, quindi suggerisco di procedere con cautela.
Lee Byron ha espresso il suo timore che la funzionalità venga usata come un anti-pattern. Tuttavia, ha anche riconosciuto che ci sono buoni utilizzi per essa. Allora, poiché la issue ha raccolto molto sostegno dalla comunità (con oltre 150 👍), agli sviluppatori potrebbe essere data l'opzione di abilitare esplicitamente l'aggiunta di un tipo Map ai loro schemi, e gestirne le conseguenze.
Feature-toggle da parte dei server GraphQL
Se la proposta di cui sopra non raccoglie sostegno perché troppo rischiosa per la spec GraphQL, un'alternativa è implementarla a livello del server GraphQL. I server GraphQL potrebbero allora fornire una funzionalità personalizzata che disabilita le ricorsioni nei fragment.
Generalizzando l'idea, i server GraphQL potrebbero offrire di disabilitare certe funzionalità della spec, e abilitarne altre che mancano nella spec. Affinché questo comportamento non produca sorprese, i server devono assicurarsi che lo stato predefinito sia quello richiesto dalla spec, e l'amministratore dell'API deve essere pienamente consapevole delle conseguenze dell'attivazione della funzionalità. (Questa è la strategia seguita da Gato GraphQL per le sue "innovative features".)
Conclusione
Mentre GraphQL è diventato sempre più popolare, nuovi framework che supportano nuove capacità lo hanno reso parte del loro stack, e nuovi stakeholder (e nuovi tipi di essi) si sono coinvolti. Allora, una specifica inizialmente creata da Facebook per definire come le sue applicazioni avrebbero ottenuto i dati dai suoi server deve sempre più confrontarsi con un maggior numero di casi d'uso.
È inevitabile che sorgano conflitti, dove un gruppo di stakeholder ha bisogno di una funzionalità che è controproducente, o persino dannosa, per altri stakeholder, come avviene con i fragment ricorsivi. Cosa si può fare per migliorare la situazione ed evitare che gli stakeholder insoddisfatti rimangano delusi da GraphQL?
Ho sostenuto che la spec potrebbe offrire la possibilità di "disabilitare" una funzionalità, consentendo agli amministratori che sanno cosa stanno facendo di rimuovere alcune limitazioni per soddisfare i propri requisiti. Ora, io stesso non sono d'accordo con questa soluzione, ma la metto comunque sul tavolo perché questa discussione deve essere fatta. Poiché questa idea è controversa, un'alternativa migliore è che i server GraphQL forniscano questo comportamento tramite funzionalità personalizzate, che devono essere esplicitamente abilitate.