Desde hai uns anos, tentei identificar marcos, produtos e servizos que permitan aos tecnólogos manter o seu foco en ampliar o valor da súa propiedade intelectual. Esta segue a ser unha viaxe marabillosa para min, chea de oportunidades únicas de aprendizaxe.
O enxeñeiro en min preguntouse recentemente se había unha situación na que puidese atopar un beneficio secundario para un concepto existente do que xa falei antes. Noutras palabras, podería identificar outro beneficio co mesmo nivel de impacto que a solución principal orixinal previamente recoñecida?
Para este artigo, quería mergullarme máis en GraphQL para ver o que podía atopar.
No meu artigo " Cando é hora de dar un descanso a REST ", falei sobre como hai escenarios do mundo real nos que GraphQL é preferible a un servizo RESTful. Explicamos como crear e implementar unha API GraphQL usando Apollo Server.
Nesta publicación de seguimento, planeo aumentar o meu coñecemento de GraphQL percorrendo subscricións para recuperar datos en tempo real. Tamén crearemos un servizo WebSocket para consumir as subscricións.
O meu artigo anterior céntrase nun caso de uso de Customer 360, onde os usuarios da miña empresa ficticia manteñen as seguintes coleccións de datos:
Unha gran vitoria ao usar GraphQL é que unha única solicitude de GraphQL pode recuperar todos os datos necesarios para o token dun cliente (identidade ú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 }
Usar un enfoque RESTful para recuperar a vista única (360) do cliente sería necesario unir varias solicitudes e respostas. GraphQL ofrécenos unha solución que funciona moito mellor.
Para subir de nivel en calquera aspecto da vida, hai que acadar novos obxectivos. Para os meus propios obxectivos aquí, isto significa:
A idea de usar subscricións sobre consultas e mutacións dentro de GraphQL é o método preferido cando se cumpren as seguintes condicións:
Isto é importante xa que implementar subscricións dentro de GraphQL non é trivial. Non só será necesario actualizar o servidor subxacente, senón que a aplicación consumidora tamén requirirá un novo deseño.
Afortunadamente, o caso de uso que estamos a buscar co noso exemplo de Customer 360 é ideal para as subscricións. Ademais, implementaremos un enfoque WebSocket para aproveitar esas subscricións.
Como antes, seguirei usando Apollo no futuro.
En primeiro lugar, necesitamos instalar as bibliotecas necesarias para admitir subscricións co meu servidor Apollo GraphQL:
npm install ws npm install graphql-ws @graphql-tools/schema npm install graphql-subscriptions
Con eses elementos instalados, centreime en actualizar o index.ts
do meu repositorio orixinal para estender a constante typedefs
co seguinte:
type Subscription { creditUpdated: Credit }
Tamén establecín unha constante para albergar unha nova instancia PubSub
e creei unha subscrición de mostra que usaremos máis tarde:
const pubsub = new PubSub(); pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: { } });
Limpei os resolutores existentes e engadín unha nova Subscription
para este novo 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']), } } };
Despois refactorei a configuración do servidor e introducín o deseño da subscrició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 actualizacións dirixidas ao cliente, creei o seguinte método para aumentar o saldo de crédito en 50 dólares cada cinco segundos mentres o servizo está funcionando. Unha vez que o saldo alcance (ou supere) o límite de crédito de 10.000 USD, restablezo o saldo a 2.500 USD, simulando que se está a realizar o pago do 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();
O ficheiro index.ts
completo pódese atopar aquí .
Co servizo listo, é hora de implementar o servizo para que poidamos interactuar con el. Dado que Heroku funcionou moi ben a última vez (e é fácil de usar para min), imos seguir con ese enfoque.
Para comezar, necesitaba executar os seguintes comandos da 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
O comando tamén engadiu automaticamente o repositorio usado por Heroku como control remoto:
$ git remote heroku origin
Como indiquei no meu artigo anterior, Apollo Server desactiva Apollo Explorer en ambientes de produción. Para manter o Apollo Explorer dispoñible para as nosas necesidades, necesitaba establecer a variable de ambiente NODE_ENV
en desenvolvemento. Configurei iso co seguinte comando CLI:
$ heroku config:set NODE_ENV=development Setting NODE_ENV and restarting ⬢ jvc-graphql-server-sub... done, v3 NODE_ENV: development
Estaba preparado para implementar o meu código en Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Unha vista rápida do panel de control de Heroku mostrou que o meu servidor Apollo funcionaba sen problemas:
Na sección Configuración, atopei o URL da aplicación Heroku para esta instancia de servizo:
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/
Teña en conta que esta ligazón deixará de estar en servizo no momento en que se publique este artigo.
Polo momento, podería engadir GraphQL a este URL para lanzar Apollo Server Studio. Isto permíteme ver as subscricións funcionando como se esperaba:
Observe as respostas de subscrición no lado dereito da pantalla.
Podemos aproveitar o soporte de WebSocket e as capacidades de Heroku para crear unha implementación que consuma a subscrición que creamos.
No meu caso, creei un ficheiro index.js co seguinte contido. Basicamente, isto creou un cliente WebSocket e tamén estableceu un servizo HTTP ficticio que podería usar para validar que o cliente estaba en execución:
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'), } );
O ficheiro index.js
completo pódese atopar aquí .
Tamén podemos implementar esta sinxela aplicación Node.js en Heroku, asegurándonos de establecer a variable de ambiente GRAPHQL_SUBSCRIPTION_HOST
no URL da aplicación Heroku que usamos anteriormente.
Tamén creei o seguinte Procfile
para dicirlle a Heroku como iniciar a miña aplicación:
web: node src/index.js
A continuación, creei unha nova 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
A continuación, configure a variable de ambiente GRAPHQL_SUBSCRIPTION_HOST
para que apunte ao meu servidor GraphQL en execución:
$ heroku --app jvc-websocket-example \ config:set \ GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql
Neste punto, estamos preparados para implementar o noso código en Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Unha vez que se inicie o cliente WebSocket, podemos ver o seu estado no panel Heroku:
Ao ver os rexistros no panel de control de Heroku para a instancia de jvc-websocket-example
, podemos ver as múltiples actualizacións da propiedade balance
do servizo jvc-graphql-server-sub
. Na miña demostración, incluso puiden capturar o caso de uso onde o saldo se reduciu a cero, simulando que se fixo un pago:
No terminal, podemos acceder a eses mesmos rexistros co comando CLI rexistros heroku.
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]: }
Non só temos un servizo GraphQL cunha implementación de subscrición en execución, senón que agora temos un cliente WebSocket que consume esas actualizacións.
Os meus lectores poden lembrar a miña declaración de misión persoal, que creo que se pode aplicar a calquera profesional de TI:
"Concentra o teu tempo en ofrecer funcións/funcionalidades que amplían o valor da túa propiedade intelectual. Aproveita marcos, produtos e servizos para todo o demais". - J. Vester
Neste mergullo profundo nas subscricións de GraphQL, consumimos con éxito as actualizacións dun servidor Apollo que se executa en Heroku mediante outro servizo que tamén se executa en Heroku: unha aplicación baseada en Node.js que usa WebSockets. Ao aproveitar as subscricións lixeiras, evitamos enviar consultas sobre datos invariables, pero simplemente subscribimos para recibir actualizacións do saldo de crédito a medida que se producían.
Na introdución, mencionei buscar un principio de valor adicional dentro dun tema sobre o que escribín antes. As subscricións de GraphQL son un excelente exemplo do que tiña en mente porque permite aos consumidores recibir actualizacións inmediatamente, sen necesidade de facer consultas cos datos de orixe. Isto fará que os consumidores dos datos de Customer 360 estean moi entusiasmados, sabendo que poden actualizar as actualizacións en directo.
Heroku é outro exemplo que segue cumprindo a miña declaración de misión ofrecendo unha plataforma que me permite prototipar rapidamente solucións usando CLI e comandos Git estándar. Isto non só me dá un xeito sinxelo de mostrar o caso de uso da miña subscrición, senón de implementar un consumidor mediante WebSockets.
Se estás interesado no código fonte deste artigo, consulta os meus repositorios en GitLab:
Síntome seguro cando digo que subín de nivel as miñas habilidades de GraphQL con este esforzo. Esta viaxe foi nova e desafiante para min... e tamén moi divertida!
A continuación planeo mergullarme na autenticación, que esperamos que ofreza outra oportunidade para subir de nivel con GraphQL e Apollo Server. Estade atentos!
Que teñades un gran día!