Hace unos años, he intentado identificar marcos, productos y servicios que permitan a los tecnólogos mantener su enfoque en ampliar el valor de su propiedad intelectual. Esto continúa siendo una maravillosa aventura para mí, llena de oportunidades de aprendizaje únicas.
La parte ingeniera en mí recientemente se preguntó si había una situación en la que podía encontrar un beneficio secundario para un concepto existente del que había hablado antes. En otras palabras, ¿podía identificar otro beneficio con el mismo nivel de impacto que la solución original reconocida anteriormente?
Para este artículo, quería profundizar más en GraphQL para ver qué podía encontrar.
En mi artículo “Cuando es el momento de darse un descanso a REST“, hablé sobre cómo existen situaciones reales en las que GraphQL es preferible a un servicio RESTful. Explicamos cómo construir y desplegar una API GraphQL usando Apollo Server.
En este artículo complementario, planeo aumentar mi conocimiento de GraphQL pasando por las suscripciones para la recuperación de datos en tiempo real. También construiremos un servicio WebSocket para consumir las suscripciones.
Resumen: Caso de uso de Customer 360
Mi artículo anterior se centró en un caso de uso de Customer 360, donde los clientes de mi ficción negocio mantienen las siguientes colecciones de datos:
- Información del cliente
- Información de direcciones
- Métodos de contacto
- Atributos de crédito
Un gran avance en el uso de GraphQL es que una sola solicitud GraphQL puede recuperar toda la información necesaria para un token de 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
}
El uso de un enfoque RESTful para obtener la visión única (360) del cliente hubiera requerido varias solicitudes y respuestas que se hubieran combinado. GraphQL nos proporciona una solución que realiza mucho mejor.
Objetivos de Nivelación
Para nivelararte en cualquier aspecto de la vida, tienes que alcanzar nuevos objetivos. Para mis propios objetivos aquí significa:
- Comprender e implementar la propuesta de valor de
suscripciones
dentro de GraphQL - Usar una implementación de WebSocket para consumir una suscripción GraphQL
La idea de usar suscripciones en lugar de consultas y mutaciones dentro de GraphQL es el método preferido cuando se cumplen las siguientes condiciones:
- Cambios incrementales pequeños a objetos grandes
- Actualizaciones en tiempo real, de bajo latencia (como una aplicación de chat)
Esto es importante ya que implementar suscripciones dentro de GraphQL no es trivial. No solo se necesitará actualizar el servidor subyacente, sino que también la aplicación consumidora requerirá un rediseño.
Afortunadamente, el caso de uso que estamos persiguiendo con nuestro ejemplo de Customer 360 es una gran coincidencia para las suscripciones. Además, implementaremos una abstracción de WebSocket para aprovechar esas suscripciones.
Como antes, continuaré usando Apollo.
Credenciales para Nivelar con Suscripciones
Primero, necesitamos instalar las bibliotecas necesarias para soportar las suscripciones con mi servidor Apollo GraphQL:
npm install ws
npm install graphql-ws @graphql-tools/schema
npm install graphql-subscriptions
Con estos elementos instalados, me fijé en actualizar el archivo index.ts
de mi repositorio original para extender la constante typedefs
con lo siguiente:
type Subscription {
creditUpdated: Credit
}
También establecí una constante para alojar una nueva instancia de PubSub
y creé un ejemplo de suscripción que usaremos más adelante:
const pubsub = new PubSub();
pubsub.publish('CREDIT_BALANCE_UPDATED', {
creditUpdated: {
}
});
Luego, limpie el existentes resolvers y agregué un nuevo 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']),
}
}
};
Después refactoré la configuración del servidor e introduce 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 iniciadas por los clientes, creé el siguiente método para aumentar el saldo de crédito en $50 cada cinco segundos mientras el servicio se está ejecutando. Una vez que el saldo alcance (o supere) el límite de crédito de $10,000, resto el saldo a $2,500, simulando 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 completo de index.ts
se puede encontrar aquí.
Deploy a Heroku
Con el servicio listo, es hora de que deployemos el servicio para interactuar con él. Dado que Heroku funcionó bien la última vez (y es fácil de usar para mí), vamos a seguir esa línea de trabajo.
Para empezar, necesité ejecutar los siguientes comandos de la 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 noté en mi artículo anterior, Apollo Server deshabilita Apollo Explorer en entornos de producción. Para mantener a Apollo Explorer disponible para nuestras necesidades, tuve que establecer la variable de entorno NODE_ENV
en desarrollo. Lo establecí con el siguiente comando de línea de comandos:
$ heroku config:set NODE_ENV=development
Setting NODE_ENV and restarting jvc-graphql-server-sub... done, v3
NODE_ENV: development
Estaba listo para desplegar mi código en Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku
Un rápido vistazo al tablero de Heroku mostró que mi servidor Apollo se ejecutaba sin ningún problema:
En la sección Settings, encontré la URL de la aplicación de Heroku para esta instancia de servicio:
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/
- Tenga en cuenta: Este enlace ya no estará en servicio el momento en que este artículo sea publicado.
Por el momento, podía agregar graphql
a esta URL para iniciar Apollo Server Studio. Esto me permitió ver que las suscripciones funcionaban como esperaba:
Observe las respuestas de las suscripciones en el lado derecho de la pantalla.
Mejorando con habilidades de WebSocket
Podemos aprovechar la compatibilidad con WebSocket y las capacidades de Heroku para crear una implementación que consume la suscripción que hemos creado.
En mi caso, cree un archivo index.js con los siguientes contenidos. Básicamente, esto creó un cliente WebSocket y también estableció un servicio HTTP simulado que podría usar para validar que el cliente estaba en ejecución:
import { createClient } from "graphql-ws";
import { WebSocket } from "ws";
import http from "http";
// Crear un servidor HTTP simulado para enlazar con 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);
}
// Suscribirse a la suscripción creditUpdated
client.subscribe(
{
query,
},
{
next: (data) => handleCreditUpdated(data.data.creditUpdated),
error: (err) => console.error('Subscription error:', err),
complete: () => console.log('Subscription complete'),
}
);
Puede encontrar el archivo completo index.js
aquí.
También podemos desplegar esta aplicación simple de Node.js en Heroku, asegurándonos de establecer la variable de entorno GRAPHQL_SUBSCRIPTION_HOST
en la URL de la aplicación de Heroku que utilizamos anteriormente.
También cree el siguiente Procfile
para decirle a Heroku cómo arrancar mi aplicación:
web: node src/index.js
A continuación, creé una nueva aplicación de 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
Después, establecí la variable de entorno GRAPHQL_SUBSCRIPTION_HOST
para apuntar 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 desplegar nuestro código a Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku
Una vez que el cliente WebSocket se inicia, podemos ver su estado en el Panel de Control de Heroku:
Al ver las entradas de registro dentro del Panel de Control de Heroku para la instancia jvc-websocket-example
, podemos ver las actualizaciones múltiples 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 hizo un pago:
En el terminal, podemos acceder a esos mismos registros con el comando CLI de Heroku 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 también tenemos un cliente WebSocket consumiendo esas actualizaciones.
Conclusión
Es posible que mis lectores recuerden mi declaración de misión personal, que creo que puede aplicarse a cualquier profesional de TI:
“Enfoca tu tiempo en entregar características/funcionalidades que extiendan el valor de tu propiedad intelectual. Utiliza frameworks, productos y servicios para todo lo demás.”
— J. Vester
En este deep-dive en las suscripciones de GraphQL, hemos consumido actualizaciones de un servidor Apollo ejecutándose en Heroku utilizando otro servicio también ejecutándose en Heroku — una aplicación basada en Node.js que utiliza WebSockets. Al aprovechar las suscripciones de bajo peso, evitamos enviar consultas para datos que no cambian y simplemente nos suscribimos para recibir actualizaciones de saldo de crédito conforme ocurrieron.
En la introducción, mencioné buscar un principio de valor adicional dentro de un tema que ya había escrito sobre antes. Las suscripciones de GraphQL son un excelente ejemplo de lo que tenía en mente ya que permite a los consumidores recibir actualizaciones inmediatamente, sin necesidad de hacer consultas contra los datos de origen. Esto hará que los consumidores de los datos del Cliente 360 sean muy emocionados, sabiendo que pueden recibir actualizaciones en vivo mientras ocurren.
Heroku es otro ejemplo que continúa adheriendo a mi declaración de misión ofreciendo una plataforma que me permite prototipar soluciones rápidamente utilizando una CLI y comandos de Git estándar. Esto no solo me da una manera fácil de mostrar mi caso de uso de suscripciones sino también implementar un consumidor utilizando WebSockets.
Si estás interesado en el código fuente para este artículo, revisa mis repositorios en GitLab:
Me siento confiado al decir que he aumentado con éxito mis habilidades en GraphQL con este esfuerzo. Este viaje fue nuevo y desafiante para mí, y también mucho divertido!
Mi próxima meta es sumergirme en la autenticación, lo que espero sea otra oportunidad para aumentar mi nivel con GraphQL y Apollo Server. ¡No te pierdas!
¡Tenga un día realmente estupendo!
Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions