Desde hace algunos años, he intentado identificar marcos, productos y servicios que permitan a los tecnólogos seguir concentrándose en ampliar el valor de su propiedad intelectual. Para mí, este sigue siendo un viaje maravilloso, lleno de oportunidades de aprendizaje únicas.
El ingeniero que llevo dentro se preguntó hace poco si existía una situación en la que pudiera encontrar un beneficio secundario para un concepto existente del que ya he hablado antes. En otras palabras, ¿podría identificar otro beneficio con el mismo nivel de impacto que la solución original reconocida anteriormente?
Para este artículo, quería profundizar en GraphQL para ver qué podía encontrar.
En mi artículo “ Cuándo es momento de dejar de lado REST ”, hablé sobre cómo existen situaciones del mundo real en las que GraphQL es preferible a un servicio RESTful. Analizamos cómo crear e implementar una API GraphQL con Apollo Server.
En esta publicación de seguimiento, planeo mejorar mi conocimiento de GraphQL al repasar las suscripciones para la recuperación de datos en tiempo real. También crearemos un servicio WebSocket para consumir las suscripciones.
Mi artículo anterior se centró en un caso de uso de Customer 360, donde los clientes de mi negocio ficticio mantienen las siguientes recopilaciones de datos:
Una gran ventaja de usar GraphQL es que una sola solicitud GraphQL puede recuperar todos los datos necesarios para el token de un cliente (identidad única).
type Query { addresses: [Address] address(customer_token: String): Address contacts: [Contact] contact(customer_token: String): Contact customers: [Customer] customer(token: String): Customer credits: [Credit] credit(customer_token: String): Credit }
Si se hubiera utilizado un enfoque RESTful para recuperar la vista única (360) del cliente, se habrían tenido que unir varias solicitudes y respuestas. GraphQL nos ofrece una solución que funciona mucho mejor.
Para ascender en cualquier aspecto de la vida, uno tiene que alcanzar nuevas metas. Para mis propias metas aquí, esto significa:
La idea de utilizar suscripciones en lugar de consultas y mutaciones dentro de GraphQL es el método preferido cuando se cumplen las siguientes condiciones:
Esto es importante porque implementar suscripciones dentro de GraphQL no es algo trivial. No solo será necesario actualizar el servidor subyacente, sino que también será necesario rediseñar la aplicación que la consume.
Afortunadamente, el caso de uso que estamos siguiendo con nuestro ejemplo de Customer 360 es ideal para las suscripciones. Además, implementaremos un enfoque WebSocket para aprovechar esas suscripciones.
Como antes, continuaré usando Apollo en el futuro.
Primero, necesitamos instalar las bibliotecas necesarias para soportar suscripciones con mi servidor Apollo GraphQL:
npm install ws npm install graphql-ws @graphql-tools/schema npm install graphql-subscriptions
Con esos elementos instalados, me concentré en actualizar el index.ts
de mi repositorio original para ampliar la constante typedefs
con lo siguiente:
type Subscription { creditUpdated: Credit }
También establecí una constante para albergar una nueva instancia PubSub
y creé una suscripción de muestra que usaremos más adelante:
const pubsub = new PubSub(); pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: { } });
Limpié los solucionadores existentes y agregué una nueva Subscription
para este nuevo caso de uso:
const resolvers = { Query: { addresses: () => addresses, address: (parent, args) => { const customer_token = args.customer_token; return addresses.find(address => address.customer_token === customer_token); }, contacts: () => contacts, contact: (parent, args) => { const customer_token = args.customer_token; return contacts.find(contact => contact.customer_token === customer_token); }, customers: () => customers, customer: (parent, args) => { const token = args.token; return customers.find(customer => customer.token === token); }, credits: () => credits, credit: (parent, args) => { const customer_token = args.customer_token; return credits.find(credit => credit.customer_token === customer_token); } }, Subscription: { creditUpdated: { subscribe: () => pubsub.asyncIterator(['CREDIT_BALANCE_UPDATED']), } } };
Luego refactoricé la configuración del servidor e introduje el diseño de suscripción:
const app = express(); const httpServer = createServer(app); const wsServer = new WebSocketServer({ server: httpServer, path: '/graphql' }); const schema = makeExecutableSchema({ typeDefs, resolvers }); const serverCleanup = useServer({ schema }, wsServer); const server = new ApolloServer({ schema, plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), { async serverWillStart() { return { async drainServer() { serverCleanup.dispose(); } }; } } ], }); await server.start(); app.use('/graphql', cors(), express.json(), expressMiddleware(server, { context: async () => ({ pubsub }) })); const PORT = Number.parseInt(process.env.PORT) || 4000; httpServer.listen(PORT, () => { console.log(`Server is now running on http://localhost:${PORT}/graphql`); console.log(`Subscription is now running on ws://localhost:${PORT}/graphql`); });
Para simular actualizaciones impulsadas por el cliente, creé el siguiente método para aumentar el saldo de crédito en $50 cada cinco segundos mientras el servicio está en funcionamiento. Una vez que el saldo alcanza (o supera) el límite de crédito de $10,000, restablezco el saldo a $2,500, simulando que se realiza un pago de saldo.
function incrementCreditBalance() { if (credits[0].balance >= credits[0].credit_limit) { credits[0].balance = 0.00; console.log(`Credit balance reset to ${credits[0].balance}`); } else { credits[0].balance += 50.00; console.log(`Credit balance updated to ${credits[0].balance}`); } pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: credits[0] }); setTimeout(incrementCreditBalance, 5000); } incrementCreditBalance();
El archivo index.ts
completo se puede encontrar aquí .
Una vez que el servicio está listo, es hora de implementarlo para poder interactuar con él. Dado que Heroku funcionó muy bien la última vez (y es fácil de usar para mí), sigamos con ese enfoque.
Para comenzar, necesitaba ejecutar los siguientes comandos CLI de Heroku:
$ heroku login $ heroku create jvc-graphql-server-sub Creating ⬢ jvc-graphql-server-sub... done https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-sub.git
El comando también agregó automáticamente el repositorio utilizado por Heroku como remoto:
$ git remote heroku origin
Como señalé en mi artículo anterior, Apollo Server desactiva Apollo Explorer en entornos de producción. Para mantener Apollo Explorer disponible para nuestras necesidades, necesitaba configurar la variable de entorno NODE_ENV
en desarrollo. Lo configuré con el siguiente comando CLI:
$ heroku config:set NODE_ENV=development Setting NODE_ENV and restarting ⬢ jvc-graphql-server-sub... done, v3 NODE_ENV: development
Estaba listo para implementar mi código en Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Una vista rápida del panel de Heroku mostró que mi servidor Apollo funcionaba sin problemas:
En la sección Configuración, encontré la URL de la aplicación Heroku para esta instancia de servicio:
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/
Tenga en cuenta que este enlace ya no estará disponible en el momento de publicar este artículo.
Por el momento, podría agregar GraphQL a esta URL para iniciar Apollo Server Studio. Esto me permitió ver que las suscripciones funcionan como se esperaba:
Observe las respuestas de suscripción en el lado derecho de la pantalla.
Podemos aprovechar el soporte de WebSocket y las capacidades de Heroku para crear una implementación que consuma la suscripción que hemos creado.
En mi caso, creé un archivo index.js con el siguiente contenido. Básicamente, esto creó un cliente WebSocket y también estableció un servicio HTTP ficticio que podía usar para validar que el cliente se estaba ejecutando:
import { createClient } from "graphql-ws"; import { WebSocket } from "ws"; import http from "http"; // Create a dummy HTTP server to bind to Heroku's $PORT const PORT = process.env.PORT || 3000; http.createServer((req, res) => res.end('Server is running')).listen(PORT, () => { console.log(`HTTP server running on port ${PORT}`); }); const host_url = process.env.GRAPHQL_SUBSCRIPTION_HOST || 'ws://localhost:4000/graphql'; const client = createClient({ url: host_url, webSocketImpl: WebSocket }); const query = `subscription { creditUpdated { token customer_token credit_limit balance credit_score } }`; function handleCreditUpdated(data) { console.log('Received credit update:', data); } // Subscribe to the creditUpdated subscription client.subscribe( { query, }, { next: (data) => handleCreditUpdated(data.data.creditUpdated), error: (err) => console.error('Subscription error:', err), complete: () => console.log('Subscription complete'), } );
El archivo index.js
completo se puede encontrar aquí .
También podemos implementar esta sencilla aplicación Node.js en Heroku, asegurándonos de configurar la variable de entorno GRAPHQL_SUBSCRIPTION_HOST
en la URL de la aplicación Heroku que usamos anteriormente.
También creé el siguiente Procfile
para indicarle a Heroku cómo iniciar mi aplicación:
web: node src/index.js
A continuación, creé una nueva aplicación Heroku:
$ heroku create jvc-websocket-example Creating ⬢ jvc-websocket-example... done https://jvc-websocket-example-62824c0b1df4.herokuapp.com/ | https://git.heroku.com/jvc-websocket-example.git
Luego, configuro la variable de entorno GRAPHQL_SUBSCRIPTION_HOST
para que apunte a mi servidor GraphQL en ejecución:
$ heroku --app jvc-websocket-example \ config:set \ GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql
En este punto, estamos listos para implementar nuestro código en Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Una vez que se inicia el cliente WebSocket, podemos ver su estado en el Dashboard de Heroku:
Al ver los registros dentro del panel de Heroku para la instancia jvc-websocket-example
, podemos ver las múltiples actualizaciones de la propiedad balance
del servicio jvc-graphql-server-sub
. En mi demostración, incluso pude capturar el caso de uso en el que el saldo se redujo a cero, simulando que se realizó un pago:
En la terminal, podemos acceder a esos mismos registros con el comando CLI heroku logs.
2024-08-28T12:14:48.463846+00:00 app[web.1]: Received credit update: { 2024-08-28T12:14:48.463874+00:00 app[web.1]: token: 'credit-token-1', 2024-08-28T12:14:48.463875+00:00 app[web.1]: customer_token: 'customer-token-1', 2024-08-28T12:14:48.463875+00:00 app[web.1]: credit_limit: 10000, 2024-08-28T12:14:48.463875+00:00 app[web.1]: balance: 9950, 2024-08-28T12:14:48.463876+00:00 app[web.1]: credit_score: 750 2024-08-28T12:14:48.463876+00:00 app[web.1]: }
No solo tenemos un servicio GraphQL con una implementación de suscripción en ejecución, sino que ahora tenemos un cliente WebSocket que consume esas actualizaciones.
Mis lectores quizá recuerden mi declaración de misión personal, que creo que puede aplicarse a cualquier profesional de TI:
“Concentre su tiempo en ofrecer características y funcionalidades que amplíen el valor de su propiedad intelectual. Aproveche los marcos, productos y servicios para todo lo demás”. — J. Vester
En este análisis profundo de las suscripciones de GraphQL, consumimos actualizaciones de un servidor Apollo que se ejecuta en Heroku mediante otro servicio que también se ejecuta en Heroku: una aplicación basada en Node.js que usa WebSockets. Al aprovechar las suscripciones livianas, evitamos enviar consultas para obtener datos invariables, sino que simplemente nos suscribimos para recibir actualizaciones de saldo de crédito a medida que se producían.
En la introducción, mencioné la búsqueda de un principio de valor adicional dentro de un tema sobre el que ya he escrito antes. Las suscripciones GraphQL son un excelente ejemplo de lo que tenía en mente, ya que permiten a los consumidores recibir actualizaciones de inmediato, sin necesidad de realizar consultas en los datos de origen. Esto hará que los consumidores de los datos de Customer 360 se sientan muy entusiasmados, sabiendo que pueden vivir las actualizaciones a medida que ocurren.
Heroku es otro ejemplo que sigue cumpliendo con mi declaración de misión al ofrecer una plataforma que me permite crear rápidamente prototipos de soluciones mediante CLI y comandos Git estándar. Esto no solo me brinda una manera sencilla de mostrar el caso de uso de mi suscripción, sino también de implementar un consumidor mediante WebSockets.
Si estás interesado en el código fuente de este artículo, consulta mis repositorios en GitLab:
Me siento seguro cuando digo que he logrado mejorar mis habilidades en GraphQL con este esfuerzo. Este viaje fue nuevo y desafiante para mí... ¡y también muy divertido!
Planeo profundizar en la autenticación a continuación, lo que espero me brinde otra oportunidad de mejorar con GraphQL y Apollo Server. ¡Estén atentos!
¡Que tengas un día genial!