Video Summary and Transcription
GraphQL ha tenido un gran impacto en la forma en que construimos aplicaciones cliente, sitios web y aplicaciones móviles. A pesar de la dominancia de los resolvers, la especificación de GraphQL no exige su uso. Presentamos Graphast, un nuevo proyecto que compila las operaciones de GraphQL en planes de ejecución y salida, proporcionando optimizaciones avanzadas. En GraphFast, en lugar de resolvers, tenemos plan resolvers que manejan datos futuros. Los plan resolvers de GraphFast son cortos y eficientes, y admiten todas las características de GraphQL moderno.
1. Introducción a GraphQL
GraphQL ha tenido un gran impacto en la forma en que construimos aplicaciones cliente, sitios web y aplicaciones móviles. Nos permite ser más eficientes, minimizar las idas y vueltas, reducir riesgos, aumentar la productividad y entregar datos en el formato que el cliente espera. Esto facilita y acelera la construcción de cosas que nuestros usuarios aman.
♪ Hola a todos. Mi nombre es Benji y me encanta GraphQL. Creo que GraphQL es increíble. Ha tenido un impacto tan grande en la forma en que construimos nuestras aplicaciones cliente, nuestros sitios web, aplicaciones móviles, y más. Nos ha mostrado cómo podemos ser más eficientes y deshacernos de este sobreconsumo y subconsumo. Realmente hacer las cosas de una manera altamente optimizada para lo que el cliente necesita. Minimizar las idas y vueltas y tener seguridad de tipo reduce los riesgos de que algo salga mal. Manejar resultados parcialmente exitosos significa que incluso si las cosas salen mal, aún podemos mostrar algo útil al usuario. La documentación incorporada aumenta la productividad. Y, por supuesto, al entregar los datos en el formato que el cliente espera, podemos minimizar la cantidad de manipulación que necesitamos hacer en el lado del cliente. Todo esto hace que sea mucho, mucho más fácil y rápido escribir nuestras aplicaciones cliente, lo que facilita y acelera la construcción de cosas que nuestros usuarios aman. ¡GraphQL es increíble! Pero tengo una confesión que hacer.
2. Desafíos con los Resolvers en GraphQL
Odio los resolvers. El lenguaje GraphQL es declarativo y, sin embargo, los resolvers convierten la ejecución en un enfoque procedural. Descartan las estrategias de optimización, requieren esfuerzo adicional para la optimización y hacen que los servidores hagan más de lo necesario. A pesar de la dominancia de los resolvers, la especificación de GraphQL no exige su uso. Podemos ejecutar operaciones de cualquier manera siempre y cuando el resultado observable sea el mismo. Los resolvers hacen cumplir la naturaleza de grafo de GraphQL, pero existen formas alternativas de ejecutar operaciones.
Odio los resolvers. Siempre he odiado los resolvers. El lenguaje GraphQL es declarativo, y sin embargo, los resolvers no fueron construidos para aprovechar esta increíble capacidad. En cambio, convierten la ejecución en un enfoque procedural, capa por capa, campo por campo, elemento por elemento. En mi opinión, los resolvers son en gran medida un enfoque de producto mínimo viable para la ejecución. Son simples de entender y especificar, pero dejan problemas como resolver el problema del N más uno en manos del usuario, requiriendo a los diseñadores de esquemas recordar el uso de abstracciones como el Data Loader para lograr un rendimiento aceptable. Y descartan clases enteras de estrategias de optimización.
Si quieres optimizar lo que le estás pidiendo a tu capa de lógica de negocio basado en la consulta GraphQL entrante, por ejemplo, seleccionar solo ciertos campos de una base de datos, o decirle a tu API remota que incluya recursos adicionales necesarios, tienes que lidiar con árboles de sintaxis abstracta o complejidades similares de anticipación o transpilación. Es desagradable y requiere mucho esfuerzo. Incluso las personas que invierten este esfuerzo tienden a hacerlo solo a un nivel bastante superficial, pero las verdaderas ganancias de eficiencia vendrían al llevar esto un poco más lejos. Todo esto significa que GraphQL hace que nuestros servidores hagan más de lo que deberían, consumiendo más ciclos de CPU, realizando más llamadas de red, utilizando más energía, ejerciendo más presión sobre el medio ambiente. Y no está haciendo tanto como podría para ahorrarnos dinero en nuestras facturas de servidor.
De hecho, mi odio por los resolvers es la razón por la que me uní al grupo de trabajo de GraphQL en primer lugar, en 2018. La especificación de GraphQL parece dictar que debemos ejecutar nuestras operaciones utilizando resolvers. Parece tan innecesario. GraphQL, siendo un lenguaje declarativo, ¿por qué debemos estipular que debemos ejecutarlo de manera procedural? A medida que fui aprendiendo la especificación de GraphQL, me di cuenta, por supuesto, de que no estipulamos que debemos usar resolvers en absoluto. Un párrafo justo al comienzo de la especificación de GraphQL, que debo admitir, cuando lo leí por primera vez, lo pasé por alto por completo, fui directamente a la sección de ejecución, establece que los requisitos de conformidad expresados como algoritmos pueden ser cumplidos por una implementación de esta especificación de cualquier manera, siempre y cuando los resultados percibidos se vean equivalentes. Estipulamos que debe parecer que se ejecuta de esa manera pero eso no necesariamente tiene que ser lo que realmente hacemos en el lado del servidor. Mientras el resultado observable sea el mismo, haz lo que quieras. Pero aún tenemos resolvers.
Los resolvers siguen siendo la forma dominante de ejecutar GraphQL. Incluso en proyectos que profundizan un poco más en la optimización de consultas del backend utilizando herramientas como mi módulo de resolución de información de GraphQL, para anticipar y descubrir qué campos están siendo solicitados, todavía estamos usando resolvers. Y la razón detrás de ellos es válida. La forma en que describen la ejecución es correcta. Sin esta definición, GraphQL podría convertirse en más un formato de transferencia que en un motor de ejecución. Los clientes no podrían confiar en las mismas suposiciones, las suposiciones que hacen posible cosas como el almacenamiento en caché normalizado. Porque los resolvers hacen cumplir la naturaleza de grafo de GraphQL, donde nos movemos de nodo a nodo, el valor del nodo en el que estamos no depende de dónde venimos ni de a dónde vamos a continuación. Y sin embargo, esta no tiene que ser la forma en que realmente ejecutamos las operaciones. He estado luchando con este problema una y otra vez durante casi seis años. He realizado muchos experimentos durante ese tiempo.
3. Experimentos y Limitaciones
He realizado muchos experimentos durante ese tiempo. Me he sumergido profundamente en la especificación de GraphQL para entender exactamente por qué se especifican los resolvers, de qué nos protegen, por qué se declaran de la manera en que lo hacen. He intentado trabajar dentro de las limitaciones de GraphQLjs para resolver este problema y he tenido cierto éxito.
He realizado muchos experimentos durante ese tiempo. Me he sumergido profundamente en la especificación de GraphQL para entender exactamente por qué se especifican los resolvers, de qué nos protegen, por qué se declaran de la manera en que lo hacen. He intentado trabajar dentro de las limitaciones de GraphQLjs para resolver este problema, y he tenido cierto éxito. Pero siempre me ha molestado que simplemente no se sienta tan ergonómico como debería ser. Las soluciones alternativas que se me han ocurrido han sido realmente torpes y rígidas. Admito que muchas de esas soluciones que se me ocurrieron fueron antes de contar con el apoyo de mis numerosos patrocinadores, por lo que no pude invertir mucho tiempo en su investigación. Pero afortunadamente, mucha gente ha encontrado que el software que escribo es muy útil. Y a medida que ha crecido mi patrocinio, gracias, patrocinadores, el tiempo que puedo invertir en este problema ha aumentado.
4. Introducción a Graphast
Hace aproximadamente dos años y medio, me propuse resolver el problema de la planificación de ejecución en GraphQL. Las bases de datos relacionales utilizan SQL, un lenguaje declarativo, para ejecutar consultas de manera eficiente. Aunque GraphQL tiene resolvers, carece de un sistema de planificación de consultas de propósito general. Presentamos Graphast, un nuevo proyecto que compila las operaciones de GraphQL en planes de ejecución y salida, proporcionando optimizaciones avanzadas. El plan de ejecución se construye utilizando pasos que representan datos futuros, similares a los data loaders. El agrupamiento es una característica fundamental de Graphast.
Hace aproximadamente dos años y medio, me propuse, a tiempo parcial, resolver este problema de una vez por todas. Pero antes de entrar en eso, hablemos de mi inspiration.
Aquellos que me conocen saben que me encantan las bases de datos relacionales. Bueno, una base de datos relacional en particular, en realidad. Las bases de datos relacionales utilizan SQL, que es un lenguaje declarativo. Especifica al servidor qué data se requiere, y el servidor decide cómo ejecutarlo. Y puede elegir muchas formas diferentes de ejecutar esa consulta. Por ejemplo, teniendo en cuenta la presencia de índices o determinando cuánta data se espera para saber qué tipo de operaciones intentar realizar. Esto se llama planificación de consultas. Incluso el Postgres moderno tiene cosas como algoritmos genéticos para ayudar a elegir el mejor plan de ejecución, compilación en tiempo real para compilar expresiones a código de máquina para que se puedan ejecutar de manera más eficiente.
Ahora, GraphQL también es declarativo. Así que tenemos resolvers. No tenemos una planificación de ejecución genérica para GraphQL. Pero no es justo decir que no tenemos nada. Muchas personas han sentido este dolor. Ahora tenemos planificadores especializados como el planificador de GraphQL a GraphQL que se encuentra en Apollo Federation, o la optimización de los internos de GraphQL en GraphQL JIT. También tenemos formas de optimizar patrones particulares como proyecciones en Hot Chocolate o transpilación de GraphQL a SQL con Hasura. Pero no tenemos un sistema de planificación de consultas de propósito general que adopte un enfoque holístico y permita optimizaciones advanced sin importar cuál sea su lógica empresarial subyacente, al menos hasta ahora.
Después de esa introducción tan larga, hoy estoy aquí para decir, aparta, resolvers. Hay una nueva forma de ejecutar cualquier operación de GraphQL. El título provisional de este nuevo proyecto es Graphast, y funciona tomando tu operación de GraphQL y compilándola en un plan de ejecución y salida. El plan de salida es un mapeo sencillo de los data que recuperamos durante la fase de ejecución al resultado de GraphQL que queremos mostrar al usuario. Así que pasaremos por alto eso. Principalmente nos interesa el plan de ejecución. Construimos el plan de ejecución siguiendo en su mayoría el algoritmo de ejecución en la especificación de GraphQL. Sin embargo, hacemos esto antes de ejecutarlo realmente. Por lo tanto, no estamos tratando con data concreta como lo haríamos en la especificación. En su lugar, estamos tratando con lo que llamamos pasos que representan estos datos futuros. Cuando llega el momento de ejecutar, un paso es bastante similar a un data loader en el sentido de que acepta una lista de entradas y devuelve una lista de salidas. El agrupamiento está integrado en el corazón mismo de GraphFast.
5. GraphFast Plan Resolvers
En GraphFast, en lugar de resolvers, tenemos plan resolvers que manejan datos futuros. Al recorrer los conjuntos de selección y llamar a los plan resolvers, podemos construir un diagrama de plan. El sistema GraphFast realiza la deduplicación, optimización y finalización del plan. La deduplicación simplifica los diagramas de plan, la optimización mejora el rendimiento y la finalización prepara el plan para su ejecución. Por ejemplo, en la API de pagos de Stripe, los pasos pueden comunicarse y pasar información para obtener datos de manera más eficiente.
Mientras que en un esquema tradicional de GraphQL un campo tendría un resolver, en GraphFast tendría un plan resolver. El plan resolver tiene un código similar, como se puede ver a simple vista. Pero en lugar de lidiar con datos concretos en tiempo de ejecución, se ocupa de datos futuros, estos pasos, que representamos con un símbolo de dólar en el código a la derecha.
Al recorrer los conjuntos de selección y llamar a los plan resolvers en cada etapa, podemos construir un diagrama de plan, como este, que expresa lo que debe suceder. Ahora, este diagrama de plan no es lo que se ejecutará finalmente, esto es solo nuestro primer borrador. El sistema GraphFast revisará cada paso en el diagrama de plan y le dará la oportunidad de deduplicarse, optimizarse y finalizarse. Estos son los tres principales métodos del ciclo de vida que estos pasos pueden implementar.
La deduplicación permite que los pasos similares se amalgamen para simplificar nuestros diagramas de plan. La optimización es la fase principal, la más crítica para el rendimiento, y permite que los pasos de la misma familia se comuniquen entre sí y pasen información entre ellos. Por ejemplo, si estamos trabajando con la API de pagos de Stripe, podríamos tener un campo de GraphQL que obtiene el cliente de Stripe. Y luego en nuestra consulta, podríamos tener un campo secundario de ese que obtiene las suscripciones del cliente, y luego varios campos debajo de eso. Cuando se optimiza el paso responsable de obtener las suscripciones, podría determinar que su abuelo está obteniendo datos de un cliente de Stripe y, por lo tanto, decirle al paso del cliente que obtenga las suscripciones al mismo tiempo. Stripe tiene una función llamada expansión para esto. Esto haría que el paso de obtención de suscripciones sea redundante. Entonces, como última acción en Optimizar, puede reemplazarse a sí mismo con un paso que simplemente extraiga los datos relevantes de la respuesta del cliente. De esta manera, ahora solo necesitamos un viaje de ida a Stripe para obtener toda la información que necesitamos.
6. Graphfast Finalize Method and Plan Resolvers
El método finalize le da a los pasos la oportunidad de prepararse para la ejecución, como compilar funciones optimizadas o preparar consultas finales de SQL o GraphQL. Los pasos pueden ser implementados por los usuarios y tienen métodos de ciclo de vida opcionales. Graphfast proporciona pasos preconstruidos para preocupaciones comunes y está diseñado para pasar información adicional a la capa de lógica empresarial para una ejecución más eficiente. Los plan resolvers de Graphfast son cortos y eficientes, admitiendo todas las características de GraphQL moderno. Graphfast es compatible con esquemas de GraphQL existentes y se planea su lanzamiento para la primera mitad de 2023 bajo la licencia MIT.
Finalmente, tenemos el método finalize. Normalmente no se utiliza para la mayoría de los pasos, pero le da al paso la oportunidad de prepararse para la ejecución, hacer algo que solo necesita hacerse una vez. Por ejemplo, se puede utilizar para compilar una función optimizada en JavaScript para ejecutar su acción de manera más eficiente. O se puede utilizar para preparar el texto final de la consulta SQL o GraphQL solo una vez.
Los pasos están diseñados para que los implementes tú mismo, al igual que lo harías con un callback de un cargador de datos. Son un poco más poderosos que un cargador de datos y tienen estos métodos de ciclo de vida opcionales que acabamos de discutir. Pero en el caso más simple, todo lo que necesitan es un método execute que tome una lista de valores y devuelva una lista de valores de la misma manera que lo hace un callback de un cargador de datos.
También tenemos una serie de pasos preconstruidos optimizados que puedes usar para manejar preocupaciones comunes, incluyendo uno para cargar en lotes recursos remotos similar a un cargador de datos, o cada uno para mapear listas o acceso para extraer propiedades de objetos. Y estamos desarrollando más pasos optimizados para tratar preocupaciones específicas. Por ejemplo, realizar solicitudes HTTP, enviar consultas GraphQL o comunicarse con bases de datos.
En última instancia, nuestra intención es utilizar estos pasos para pasar información adicional a tu capa de lógica empresarial, sin importar qué sea, para que pueda ejecutar sus tareas de manera más eficiente. Al igual que GraphQL ayuda a eliminar la sobre y la sub-solicitud en el lado del cliente, Graphfast te ayuda a eliminarlo en el lado del servidor. Por ejemplo, si tu lógica empresarial está implementada con un ORM, o algo similar, puedes utilizar esta información adicional para realizar una carga ansiosa selectiva y reducir las consultas a la base de datos. Si tu lógica empresarial proviene de una API HTTP, puedes utilizar esta información contextual para dictar qué parámetros pasar, controlando mejor los datos que estás obteniendo, reduciendo la carga del servidor y el tráfico de red. Y dado que Graphfast está diseñado en torno al concepto de agrupación, nunca más tendrás que preocuparte por el problema de N más uno. Está resuelto para ti, de manera predeterminada, gracias a cómo funciona el sistema.
Al igual que con los resolvers de GraphQL, los plan resolvers de Graphfast están diseñados para ser cortos y solo expresar lo que es necesario para comunicarse entre GraphQL y tu lógica empresarial. Y a pesar de su agradable ergonomía, desbloquean una eficiencia y rendimiento mucho mayores que los resolvers. Graphfast ha sido construido desde cero para admitir todas las características de GraphQL moderno. Consultas, mutaciones, suscripciones, polimorfismo e incluso tecnología de vanguardia como las directivas de stream y defer. Y es compatible con esquemas de GraphQL existentes. Por lo tanto, puedes usar Graphfast para ejecutar solicitudes contra un esquema basado en resolvers existente y luego migrar a plan resolvers campo por campo. Y si ya utilizas Dataloader, migrar a usar Graphfast plan debería ser muy sencillo.
Esperamos lanzar una versión de código abierto de Graphfast bajo la licencia MIT, la misma licencia que utiliza GraphQL.js, en la primera mitad de 2023. Para recibir una notificación cuando estemos listos, ingresa tu dirección de correo electrónico en graphfast.org. Si deseas ayudarme a seguir invirtiendo tiempo en proyectos como este, considera patrocinarme en GitHub sponsors. Incluso podrías obtener acceso anticipado. No dudes en comunicarte conmigo si tienes alguna pregunta. Gracias por tu tiempo y espero que estés tan emocionado acerca del futuro de GraphQL como yo. Gracias. Microsoft Mechanics www.microsoft.com.com
Comments