🤔 ¿Debería GraphQL ser diferente para distintos usuarios?
GraphQL es una interfaz para recuperar datos de algún origen, con la spec de GraphQL definiendo los requisitos para la interfaz. Mientras se satisfagan estos requisitos, a GraphQL no le importa cómo se logre. El servidor GraphQL puede entonces implementarse en JavaScript usando promesas, usando una arquitectura concurrente basada en Golang, mapeado a un fichero Excel, o lo que sea, y todas estas pueden ser implementaciones válidas de la spec de GraphQL.

Cómo se implementa el motor del servidor no es importante para la ejecución exitosa de una petición GraphQL, ya que la interacción entre cliente y servidor es siempre la misma, ocurriendo al enviar una consulta GraphQL usando una sintaxis definida, y obteniendo una respuesta correspondiente en formato JSON.
Ahora, cuando digo que la implementación no es importante, lo digo desde la perspectiva del usuario de la API, que simplemente pretende obtener datos del servidor. Cómo se produjeron los datos devueltos no es de interés.
Pero la situación cambia para el desarrollador del lado del servidor que trabaja en la API, para quien los detalles de la implementación son de hecho muy importantes. Si programo mi API GraphQL en PHP, entonces haré lo posible para que mi API se resuelva tan eficientemente como sea posible, y para tener un diseño arquitectónico que sea lo más elegante posible, usando las capacidades ofrecidas por PHP.

Entonces, tenemos un posible conflicto de intereses entre la necesidad de salvaguardar la API, y las capacidades esperadas por los desarrolladores que trabajan en la API, que no quieren que les quiten características soportadas por el lenguaje subyacente (como poder ejecutar código recursivo).
Este conflicto se hizo evidente en el issue #929: Allow recursive references in fragments, que sostiene que GraphQL no debería prohibir las recursiones en fragments.
En un meetup pasado del grupo de trabajo de GraphQL, Roman, que es el desarrollador que planteó el issue, expresó por qué está en desacuerdo con la limitación impuesta por la spec:
Soy desarrollador del lado del servidor, y siento que la spec habla demasiado de la ejecución del lado del servidor, mientras que debería centrarse en lo que el cliente quiere que se le entregue - no en cómo
La regla que prohíbe las recursiones en fragments se ha justificado bajo la premisa de mantener segura la API pública. Después de todo, GraphQL se creó por Facebook para entregar datos a su aplicación de cara al público, y los usuarios no deberían ser capaces de explotar un fallo en el diseño de la API que pudiera echar abajo el servicio.
El creador de GraphQL, Lee Byron, expresó tres preocupaciones principales:
recursión infinita; las limitaciones no serían solo especificación - cómo debería pararse y cuándo
validación de datos; devolver el mismo valor varias veces, cómo se representa eso en los datos. Idealmente quieres detectar que es cíclico y parar inmediatamente, pero algunos servidores no pueden detectar esto y pueden hacer bucle muchas veces antes de detectar que algo fue mal y parar
¿cuál es el coste de no tener esto?; ¿justifica estos problemas? No lo hace; siempre es posible especificar el número de niveles de profundidad en tu consulta - eso es efectivamente la versión sin azúcar de lo que haríamos si lo manejásemos en GraphQL
Viniendo de sus propias perspectivas, tanto Roman como Lee tienen razón. Lee Byron está preocupado por la seguridad de la API GraphQL pública. Evitar fragments recursivos se justifica para asegurar que ningún actor malicioso pueda tumbar el sistema ejecutando un bucle cíclico interminable en la consulta, e incluso eliminar la posibilidad de un "self-DDoSing" de un equipo, que podría ocurrir si publican involuntariamente una consulta que detiene el sistema.
Roman, sin embargo, está preocupado por las limitaciones a sus propias capacidades para crear una API GraphQL. Como Roman puede ser el único consumidor de su API (es decir, una API privada que no está expuesta a usuarios), o porque su servidor puede tener la capacidad de detectar y parar ciclos recursivos, entonces cree que la limitación de GraphQL es perjudicial y no justificable.
En el núcleo de la discusión, el problema no es si los fragments recursivos deberían permitirse o no, sino algo más fundamental: ¿Quién es el objetivo de GraphQL? Si no es un único grupo, ¿podría una única especificación de API satisfacer los requisitos de todos los distintos stakeholders? Y si el conflicto no puede prevenirse, ¿puede al menos remediarse de alguna manera?
Exploremos estas preguntas.
¿Quién es el objetivo de GraphQL?
GraphQL está siendo usado por distintos tipos de stakeholders, entre los que podemos identificar:
1. Usuarios de la API: Aquellos que consumen datos de algún endpoint GraphQL, por la razón que sea. Por ejemplo, todos podemos ser usuarios de la API pública GraphQL de GitHub, para recuperar datos sobre nuestros repos de GitHub.
2. Desarrolladores del lado del cliente: Aquellos que crean aplicaciones del lado del cliente que se potencian con algún endpoint GraphQL. Por ejemplo, los desarrolladores que construyen sitios con Gatsby confían en GraphQL para obtener el contenido del sitio.
3. Desarrolladores backend: Aquellos que crean los resolvers para la API GraphQL.
Además, debemos tener en cuenta que la API GraphQL puede ser pública o privada:
API pública: Como cualquiera tiene acceso al endpoint GraphQL, debemos preocuparnos por medidas de seguridad para evitar ataques de actores maliciosos.
API privada: Como solo los actores previstos tienen acceso a la API, no hay riesgos de seguridad inherentes, y el self-DDoSing puede evitarse fácilmente con buenas prácticas de codificación.
¿Satisface una única especificación de API los requisitos de todos los stakeholders?
El issue planteado por Roman puede interpretarse así: "Si mi API GraphQL es privada, y sé exactamente lo que estoy haciendo (teniendo el 100 % de certeza de que mi código funcionará como se espera y no se producirán ejecuciones bloqueantes), entonces ¿por qué no puedo usar recursiones en fragments?"

Un ejemplo de esta situación ocurre cuando usamos un framework potenciado por GraphQL para construir sitios estáticos (como Gatsby, Next.js o RedwoodJS), porque la API GraphQL a menudo será privada, y no podemos inadvertidamente hacer DDoS de nuestra aplicación y sufrir consecuencias adversas (como máximo se caerá al construir el sitio estático en un entorno de desarrollo o staging).
Los desarrolladores que usen la configuración anterior pueden perfectamente preguntarse por qué la spec de GraphQL les prohíbe usar características beneficiosas, que no tienen ninguna consecuencia adversa para su configuración.
En conclusión, al prohibir fragments recursivos, la spec de GraphQL está imponiendo una medida de seguridad que se aplica a una selección de todos los usos potenciales de GraphQL, no a todos ellos, para estar del lado seguro.
¿Podría la spec de GraphQL satisfacer mejor a todos los stakeholders?
Si diferentes stakeholders tienen diferentes requisitos, ¿cómo puede la spec de GraphQL satisfacer a todos ellos? (La idea es evitar hacer un fork de la spec y producir versiones personalizadas para objetivos específicos.)
Exploremos un par de ideas, donde la primera tendría que pasar por el proceso de contribución a la spec, mientras que la segunda no.
Feature-toggle a nivel de la spec de GraphQL
Una posible ruta es que la spec "sugiera" pero no "imponga" reglas. En este caso, la regla que prohíbe las recursiones en fragments podría ser fuertemente sugerida, pero la característica seguiría siendo aceptada.
Ahora, esta solución cambiaría la condición por defecto de los fragments recursivos de "obligatoria" a "opcional", lo que produciría dos consecuencias negativas:
- La API sería insegura por defecto (el escenario que Lee Byron quiere evitar)
- Produciría un breaking change, ya que una consulta prohibida sería entonces permitida
Entonces, sería mejor invertir la opción, manteniendo las recursiones en fragments aún prohibidas por defecto pero dando la posibilidad de activar un feature-flag que deshabilita este comportamiento. Como la característica debe ser deshabilitada explícitamente, solo lo harán los admins que sepan lo que están haciendo.
Como la característica es más valiosa bajo ciertas configuraciones, los servidores y frameworks GraphQL podrían decidir si/cómo/cuándo ofrecer la configuración. Por ejemplo, Gatsby podría mostrar prominentemente la opción a través de alguna UI cuando se creen sitios estáticos, y ocultarla en caso contrario.
La idea general es que la spec de GraphQL soporte "características habilitadas pero opcionales", que pueden habilitarse/deshabilitarse vía configuración, y su estado por defecto es el que ya tienen en la spec.
Prohibir fragments recursivos sería una de ellas, y podría haber otras como esa también, como un tipo Map, que no fue aceptado para la spec por Lee Byron porque:
Hay compromisos significativos para un tipo Map vs una lista de pares clave/valor. Un problema es paginar sobre la colección. Las listas de valores pueden tener reglas claras de paginación mientras que los Maps que a menudo tienen pares clave-valor no ordenados son mucho más difíciles de paginar.
Otro problema es el uso. La mayoría de las veces Map se usa dentro de APIs donde un campo del valor está siendo indexado, lo cual es en mi opinión un anti-pattern de API ya que la indexación es un problema de almacenamiento y un problema de caching del cliente pero no un problema de transporte. Este anti-pattern me preocupa. Aunque hay buenos usos para Maps en APIs, temo que el uso común será para estos anti-patterns así que sugiero proceder con precaución.
Lee Byron expresó su miedo de que la característica se use como un anti-pattern. Sin embargo, también reconoció que hay buenos usos para ella. Entonces, como el issue obtuvo bastante apoyo de la comunidad (con más de 150 👍), a los desarrolladores se les podría dar la opción de habilitar explícitamente la adición de un tipo Map a sus esquemas, y lidiar con las consecuencias.
Feature-toggle por los servidores GraphQL
Si la propuesta de arriba no reúne apoyo ya que es demasiado arriesgada para la spec de GraphQL, una alternativa es implementarla a nivel del servidor GraphQL. Entonces, los servidores GraphQL podrían proporcionar una característica personalizada que deshabilita las recursiones en fragments.
Generalizando la idea, los servidores GraphQL podrían ofrecer deshabilitar ciertas características de la spec, y habilitar otras que faltan en la spec. Para que este comportamiento no produzca sorpresas, los servidores deben asegurarse de que el estado por defecto sea el requerido por la spec, y el admin de la API debe ser plenamente consciente de las consecuencias de activar la característica. (Esta es la estrategia seguida por Gato GraphQL para sus "innovative features".)
Cerrando
Como GraphQL ha ido haciéndose cada vez más popular, nuevos frameworks que soportan nuevas capacidades lo han hecho parte de su stack, y nuevos stakeholders (y nuevos tipos de ellos) se han involucrado. Entonces, una especificación inicialmente creada por Facebook para definir cómo sus aplicaciones obtendrían datos de sus servidores necesita lidiar cada vez más con más casos de uso.
Es inevitable que surjan conflictos, donde un conjunto de stakeholders necesita una característica que es contraproducente, o incluso perjudicial, para otros stakeholders, como es el caso con los fragments recursivos. ¿Qué se puede hacer para mejorar la situación, y evitar que los stakeholders insatisfechos no se decepcionen con GraphQL?
He argumentado que la spec podría ofrecer la oportunidad de "deshabilitar" una característica, permitiendo a los admins que saben lo que están haciendo eliminar algunas limitaciones para satisfacer sus propios requisitos. Ahora, yo mismo no estoy de acuerdo con esta solución, pero la saco a la luz no obstante porque esta discusión es necesaria. Como esta idea es controvertida, una mejor alternativa es que los servidores GraphQL proporcionen este comportamiento vía características personalizadas, que deben ser habilitadas explícitamente.