Lo que aprendí sobre GraphQL mientras desarrollaba una aplicación de Shopify (Parte 2)

Agustin Queirolo
.
March 8, 2023
Lo que aprendí sobre GraphQL mientras desarrollaba una aplicación de Shopify (Parte 2)

En parte 1 discutimos qué es exactamente GraphQL y cómo se usó en nuestro proyecto para enviar solicitudes al servidor GraphQL de Shopify. Nuestro equipo se dio cuenta rápidamente de que también necesitábamos implementar un servidor interno para nuestra aplicación Shopify, a fin de gestionar las solicitudes, las autorizaciones, las operaciones de la base de datos, etc.

Dado que el código estándar Node.js proporcionado por Shopify que mencionamos en la parte 1 incluye un pequeño servidor GraphQL, tenía sentido usarlo como base y adaptarlo a nuestras necesidades. Un servidor GraphQL requiere un conjunto de herramientas y bibliotecas diferente al de un servidor REST. Nuestro trabajo consistió en entender qué tecnologías y conceptos se utilizan para implementar este tipo de servidor, cómo funciona el servidor estándar de Shopify y cómo podemos cambiarlo para agregar nuevas funcionalidades. En esta última parte de nuestro blog repasaremos todo esto con la esperanza de que también lo encuentres útil.

Tecnologías y conceptos

Apolo

El código que Shopify proporcionó ya viene con un servidor creado con Apolo, un conjunto de bibliotecas para trabajar con GraphQL. Apollo facilita la creación y el mantenimiento de servidores GraphQL al proporcionar una API unificada y un conjunto de prácticas recomendadas para implementar GraphQL. Con Apollo, los desarrolladores pueden centrarse en escribir la lógica de sus API, mientras que Apollo se ocupa de los detalles de la gestión de las solicitudes y respuestas de GraphQL. En este post vamos a hablar de Apollo Server, su biblioteca para implementar servidores GraphQL.

Esquema

En la parte 1 mencionamos cómo un esquema define la estructura de los datos que se pueden consultar en un servidor GraphQL. Este es un breve ejemplo de esquema que define dos tipos de objetos: libro y autor (cortesía de la documentación de Apollo):

no-line-numbers|graphqltype Book { title: String author: Author} type Author { name: String books: [Book]}

Este esquema define una colección de tipos y las relaciones entre esos tipos. En el esquema de ejemplo anterior, un libro puede tener un autor asociado y un autor puede tener una lista de libros.

El tipo de consulta es un tipo de objeto especial que define todos los puntos de entrada de nivel superior para las consultas que los clientes ejecutan en su servidor. Cada campo del tipo de consulta define el nombre y el tipo de devolución de un punto de entrada diferente. Vamos a echar un vistazo al siguiente ejemplo:

no-line-numbers|graphqltype Query { books: [Book] authors: [Author]}

Este tipo de consulta define dos campos: libros y autores. Cada campo devuelve una lista del tipo correspondiente. Con una API basada en REST, es probable que los libros y los autores se devuelvan desde diferentes puntos finales (por ejemplo, /api/books y /api/authors). La flexibilidad de GraphQL permite a los clientes consultar ambos recursos con una sola solicitud.

Hay otro tipo llamado mutación. Es similar en estructura y propósito a la Tipo de consulta. Mientras que el tipo de consulta define los puntos de entrada para las operaciones de lectura, el tipo de mutación define los puntos de entrada para las operaciones de escritura.

no-line-numbers|graphqltype Mutation { addBook(title: String, author:String): Book}

Este tipo de mutación define una única mutación disponible, AddBook. La mutación acepta dos argumentos (título y autor) y devuelve un objeto Book recién creado.

Si desea obtener más información sobre los esquemas, como el tipo de suscripción, consulte la documentación de Apollo Server.

Veamos cómo se definió inicialmente el esquema en el código repetitivo de Shopify.

no-line-numbers|typescriptimport {gql} from 'apollo-server-express';export const typeDefs = `type AdminShop { id: ID! domain: String! publicationId: String! appHandle: String! availableProductCount: Int! onboardingInfoCompleted: Boolean! termsAccepted: Boolean! onboardingCompleted: Boolean!}type Shop { id: ID! country: String! domain: String! name: String! storefrontAccessToken: String!}type Query { adminShop: AdminShop! shop(id: Int!): Shop shops(country: String, nameIsLike: String, reverse: Boolean, domains: [String]): [Shop] shopCountries: [String]}type Mutation { completeOnboardingInfo: AdminShop acceptTerms: AdminShop completeOnboarding: AdminShop}`;export const schema = gql(typeDefs);

Este esquema define dos tipos de objetos: AdminShop y Shop, así como diferentes consultas y mutaciones. Si el cliente quiere recuperar la información de una tienda o cambiar la base de datos para indicar que el proceso de incorporación se ha completado, este esquema proporciona los puntos de entrada necesarios. Un esquema es un concepto muy poderoso y solo hemos esbozado la superficie de lo que se puede definir en él, pero una cosa que no se puede hacer dentro de un esquema es proporcionar la lógica necesaria para gestionar estas consultas y mutaciones. Para ello necesitamos implementar solucionadores.

Resolver

Un solucionador es una función que se encarga de rellenar los datos de un solo campo del esquema. Definimos todos los solucionadores en un único archivo. Digamos, por ejemplo, que solo queremos definir un solucionador para la tienda (id: Int!) : Consulta de tienda que definimos anteriormente. El solucionador resultante tendría este aspecto:

no-line-numbers|typescriptexport const resolvers = { Query: { shop: async (_, {id}) => { if (id) { const shop = db.Shop.findOne({ where: { id, }, }); return shop; } return null; }, }, };

La implementación es bastante simple, solo usamos el identificador que obtuvimos como parámetro para encontrar una tienda en una base de datos y devolver el resultado.

¿Cómo podemos cambiar lo que Shopify nos ofrece?

Ahora que tenemos un conocimiento básico de cómo implementar un servidor GraphQL y tenemos el esquema y los resolutores de Shopify disponibles, podemos empezar a adaptar el servidor a nuestras necesidades. Es muy probable que las necesidades de tu servidor no coincidan al 100% con la implementación genérica que Shopify ha proporcionado tan amablemente, por lo que tendrás que hacer algunos cambios.

Imaginemos que esperas que dos tipos muy distintos de tiendas instalen tu aplicación Shopify: tiendas que venden ropa de invierno y tiendas que no lo hacen. Queremos implementar una forma de hacer esa distinción. Vayamos primero a nuestro esquema, a la definición del tipo Shop. Vamos a incluir una bandera booleana llamada 'sellSwinterClothes' para ese tipo. Como se trata de un campo muy importante, tiene sentido incluir un signo de exclamación (!) lo que significa que el campo no admite valores NULL. Así que nuestro tipo Shop ahora tendría este aspecto:

no-line-numbers|graphqltype Shop { id: ID! country: String! domain: String! name: String! storefrontAccessToken: String! sellsWinterClothes: Boolean!}

Ahora, cada vez que el cliente utilice las consultas «tienda» o «tiendas» que ya están definidas, también se devolverá esta información. Lo único que debe hacer el cliente es especificar que quiere recuperar el campo «SellSwinterClothes» de su consulta, como se explica en la primera parte de esta entrada de blog. Si no se realiza ningún cambio en el lado del cliente, este no recibirá el nuevo campo, pero no se producirá ningún cambio importante. Pueden continuar como de costumbre. Esa es una de las ventajas de GraphQL que mencionamos en la primera parte del blog.

Si echamos un vistazo al solucionador de la consulta de la tienda, no es necesario cambiar ni una sola línea de código. Solo necesitamos asegurarnos de que la base de datos devuelva el atributo «sellSwinterClothes» cuando consultemos la tabla Shop. Si pretendiéramos definir una nueva consulta o mutación (por ejemplo, cambiar ese campo booleano) en nuestro esquema, tendríamos que añadirle un solucionador.

En resumen, si queremos hacer cambios en un servidor GraphQL primero tenemos que ir al esquema. Allí, podemos crear o cambiar un tipo de objeto, añadir consultas o mutaciones (o incluso más cosas de las que no hemos hablado aquí). Posteriormente, se deben realizar los cambios correspondientes en el archivo de resolución.

Conclusión

En la segunda parte de esta entrada del blog, hemos profundizado en cómo podemos construir un servidor GraphQL. Hemos analizado algunos de los conceptos básicos de GraphQL, como los esquemas y los resolutores. Por último, hemos discutido cómo sacar el máximo provecho del servidor Node.js que Shopify nos ha proporcionado y cómo adaptarlo a nuestras necesidades específicas.

GraphQL ofrece una forma más eficiente y flexible de solicitar y recibir datos de las API, en comparación con el REST tradicional. Su capacidad para gestionar múltiples consultas en una sola solicitud y su lenguaje de consulta fácil de usar son características destacables. Aunque al principio pueda parecer intimidante, es una gran herramienta que te permitirá sacar el máximo provecho de tu aplicación Shopify.

Manténgase a la vanguardia de las últimas tendencias y conocimientos sobre big data, aprendizaje automático e inteligencia artificial. ¡No se lo pierda y suscríbase a nuestro boletín de noticias!

En parte 1 discutimos qué es exactamente GraphQL y cómo se usó en nuestro proyecto para enviar solicitudes al servidor GraphQL de Shopify. Nuestro equipo se dio cuenta rápidamente de que también necesitábamos implementar un servidor interno para nuestra aplicación Shopify, a fin de gestionar las solicitudes, las autorizaciones, las operaciones de la base de datos, etc.

Dado que el código estándar Node.js proporcionado por Shopify que mencionamos en la parte 1 incluye un pequeño servidor GraphQL, tenía sentido usarlo como base y adaptarlo a nuestras necesidades. Un servidor GraphQL requiere un conjunto de herramientas y bibliotecas diferente al de un servidor REST. Nuestro trabajo consistió en entender qué tecnologías y conceptos se utilizan para implementar este tipo de servidor, cómo funciona el servidor estándar de Shopify y cómo podemos cambiarlo para agregar nuevas funcionalidades. En esta última parte de nuestro blog repasaremos todo esto con la esperanza de que también lo encuentres útil.

Tecnologías y conceptos

Apolo

El código que Shopify proporcionó ya viene con un servidor creado con Apolo, un conjunto de bibliotecas para trabajar con GraphQL. Apollo facilita la creación y el mantenimiento de servidores GraphQL al proporcionar una API unificada y un conjunto de prácticas recomendadas para implementar GraphQL. Con Apollo, los desarrolladores pueden centrarse en escribir la lógica de sus API, mientras que Apollo se ocupa de los detalles de la gestión de las solicitudes y respuestas de GraphQL. En este post vamos a hablar de Apollo Server, su biblioteca para implementar servidores GraphQL.

Esquema

En la parte 1 mencionamos cómo un esquema define la estructura de los datos que se pueden consultar en un servidor GraphQL. Este es un breve ejemplo de esquema que define dos tipos de objetos: libro y autor (cortesía de la documentación de Apollo):

no-line-numbers|graphqltype Book { title: String author: Author} type Author { name: String books: [Book]}

Este esquema define una colección de tipos y las relaciones entre esos tipos. En el esquema de ejemplo anterior, un libro puede tener un autor asociado y un autor puede tener una lista de libros.

El tipo de consulta es un tipo de objeto especial que define todos los puntos de entrada de nivel superior para las consultas que los clientes ejecutan en su servidor. Cada campo del tipo de consulta define el nombre y el tipo de devolución de un punto de entrada diferente. Vamos a echar un vistazo al siguiente ejemplo:

no-line-numbers|graphqltype Query { books: [Book] authors: [Author]}

Este tipo de consulta define dos campos: libros y autores. Cada campo devuelve una lista del tipo correspondiente. Con una API basada en REST, es probable que los libros y los autores se devuelvan desde diferentes puntos finales (por ejemplo, /api/books y /api/authors). La flexibilidad de GraphQL permite a los clientes consultar ambos recursos con una sola solicitud.

Hay otro tipo llamado mutación. Es similar en estructura y propósito a la Tipo de consulta. Mientras que el tipo de consulta define los puntos de entrada para las operaciones de lectura, el tipo de mutación define los puntos de entrada para las operaciones de escritura.

no-line-numbers|graphqltype Mutation { addBook(title: String, author:String): Book}

Este tipo de mutación define una única mutación disponible, AddBook. La mutación acepta dos argumentos (título y autor) y devuelve un objeto Book recién creado.

Si desea obtener más información sobre los esquemas, como el tipo de suscripción, consulte la documentación de Apollo Server.

Veamos cómo se definió inicialmente el esquema en el código repetitivo de Shopify.

no-line-numbers|typescriptimport {gql} from 'apollo-server-express';export const typeDefs = `type AdminShop { id: ID! domain: String! publicationId: String! appHandle: String! availableProductCount: Int! onboardingInfoCompleted: Boolean! termsAccepted: Boolean! onboardingCompleted: Boolean!}type Shop { id: ID! country: String! domain: String! name: String! storefrontAccessToken: String!}type Query { adminShop: AdminShop! shop(id: Int!): Shop shops(country: String, nameIsLike: String, reverse: Boolean, domains: [String]): [Shop] shopCountries: [String]}type Mutation { completeOnboardingInfo: AdminShop acceptTerms: AdminShop completeOnboarding: AdminShop}`;export const schema = gql(typeDefs);

Este esquema define dos tipos de objetos: AdminShop y Shop, así como diferentes consultas y mutaciones. Si el cliente quiere recuperar la información de una tienda o cambiar la base de datos para indicar que el proceso de incorporación se ha completado, este esquema proporciona los puntos de entrada necesarios. Un esquema es un concepto muy poderoso y solo hemos esbozado la superficie de lo que se puede definir en él, pero una cosa que no se puede hacer dentro de un esquema es proporcionar la lógica necesaria para gestionar estas consultas y mutaciones. Para ello necesitamos implementar solucionadores.

Resolver

Un solucionador es una función que se encarga de rellenar los datos de un solo campo del esquema. Definimos todos los solucionadores en un único archivo. Digamos, por ejemplo, que solo queremos definir un solucionador para la tienda (id: Int!) : Consulta de tienda que definimos anteriormente. El solucionador resultante tendría este aspecto:

no-line-numbers|typescriptexport const resolvers = { Query: { shop: async (_, {id}) => { if (id) { const shop = db.Shop.findOne({ where: { id, }, }); return shop; } return null; }, }, };

La implementación es bastante simple, solo usamos el identificador que obtuvimos como parámetro para encontrar una tienda en una base de datos y devolver el resultado.

¿Cómo podemos cambiar lo que Shopify nos ofrece?

Ahora que tenemos un conocimiento básico de cómo implementar un servidor GraphQL y tenemos el esquema y los resolutores de Shopify disponibles, podemos empezar a adaptar el servidor a nuestras necesidades. Es muy probable que las necesidades de tu servidor no coincidan al 100% con la implementación genérica que Shopify ha proporcionado tan amablemente, por lo que tendrás que hacer algunos cambios.

Imaginemos que esperas que dos tipos muy distintos de tiendas instalen tu aplicación Shopify: tiendas que venden ropa de invierno y tiendas que no lo hacen. Queremos implementar una forma de hacer esa distinción. Vayamos primero a nuestro esquema, a la definición del tipo Shop. Vamos a incluir una bandera booleana llamada 'sellSwinterClothes' para ese tipo. Como se trata de un campo muy importante, tiene sentido incluir un signo de exclamación (!) lo que significa que el campo no admite valores NULL. Así que nuestro tipo Shop ahora tendría este aspecto:

no-line-numbers|graphqltype Shop { id: ID! country: String! domain: String! name: String! storefrontAccessToken: String! sellsWinterClothes: Boolean!}

Ahora, cada vez que el cliente utilice las consultas «tienda» o «tiendas» que ya están definidas, también se devolverá esta información. Lo único que debe hacer el cliente es especificar que quiere recuperar el campo «SellSwinterClothes» de su consulta, como se explica en la primera parte de esta entrada de blog. Si no se realiza ningún cambio en el lado del cliente, este no recibirá el nuevo campo, pero no se producirá ningún cambio importante. Pueden continuar como de costumbre. Esa es una de las ventajas de GraphQL que mencionamos en la primera parte del blog.

Si echamos un vistazo al solucionador de la consulta de la tienda, no es necesario cambiar ni una sola línea de código. Solo necesitamos asegurarnos de que la base de datos devuelva el atributo «sellSwinterClothes» cuando consultemos la tabla Shop. Si pretendiéramos definir una nueva consulta o mutación (por ejemplo, cambiar ese campo booleano) en nuestro esquema, tendríamos que añadirle un solucionador.

En resumen, si queremos hacer cambios en un servidor GraphQL primero tenemos que ir al esquema. Allí, podemos crear o cambiar un tipo de objeto, añadir consultas o mutaciones (o incluso más cosas de las que no hemos hablado aquí). Posteriormente, se deben realizar los cambios correspondientes en el archivo de resolución.

Conclusión

En la segunda parte de esta entrada del blog, hemos profundizado en cómo podemos construir un servidor GraphQL. Hemos analizado algunos de los conceptos básicos de GraphQL, como los esquemas y los resolutores. Por último, hemos discutido cómo sacar el máximo provecho del servidor Node.js que Shopify nos ha proporcionado y cómo adaptarlo a nuestras necesidades específicas.

GraphQL ofrece una forma más eficiente y flexible de solicitar y recibir datos de las API, en comparación con el REST tradicional. Su capacidad para gestionar múltiples consultas en una sola solicitud y su lenguaje de consulta fácil de usar son características destacables. Aunque al principio pueda parecer intimidante, es una gran herramienta que te permitirá sacar el máximo provecho de tu aplicación Shopify.

Manténgase a la vanguardia de las últimas tendencias y conocimientos sobre big data, aprendizaje automático e inteligencia artificial. ¡No se lo pierda y suscríbase a nuestro boletín de noticias!