Há alguns anos, tenho tentado identificar frameworks, produtos e serviços que permitem aos tecnólogos manter seu foco na ampliação do valor da sua propriedade intelectual. Essa continua sendo uma viagem maravilhosa para mim, cheia de oportunidades de aprendizagem únicas.
O engenheiro em mim recentemente questionou se existia alguma situação onde eu poderia encontrar um benefício secundário para um conceito existente que tinha falado antes. Noutras palavras, poderia eu identificar outro benefício com o mesmo nível de impacto que a solução mãe original reconhecida anteriormente?
Para esse artigo, eu quis mergulhar mais fundo em GraphQL para ver o que eu poderia encontrar.
No meu artigo “When It’s Time to Give REST a Rest“, eu falei sobre como existem cenários reais do mundo real em que GraphQL é preferível a um serviço RESTful. Passamos por como construir e implantar um API GraphQL usando o Apollo Server.
Nesta postagem subsequente, eu planejo aprimorar meu conhecimento de GraphQL passando por assinaturas para a recuperação de dados em tempo real. Também vamos construir um serviço WebSocket para consumir as assinaturas.
Resumo: Cenário de Uso do Customer 360
Meu artigo anterior se centrou em um cenário de uso do Customer 360, onde os clientes da minha empresa ficcional mantêm as seguintes coleções de dados:
- Informações do cliente
- Informações de endereço
- Métodos de contato
- Atributos de crédito
Uma grande vantagem em usar GraphQL é que uma única solicitação GraphQL pode recuperar todos os dados necessários para um token do 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
}
Usando uma abordagem RESTful para recuperar a visão única (360) do cliente teria requerido várias solicitações e respostas para serem juntadas. O GraphQL oferece-nos uma solução que funciona muito melhor.
Meta de Aumento de Nível
Para avançar em qualquer aspecto da vida, é preciso alcançar novos objetivos. Para os meus próprios objetivos aqui, isso significa:
- Entender e implementar a oferta de valor das
assinaturas
dentro do GraphQL - Usando uma implementação de WebSocket para consumir uma assinatura GraphQL
A ideia de usar assinaturas em vez de consultas e mutações dentro do GraphQL é o método preferido quando as seguintes condições forem atendidas:
- Pequenas mudanças incrementais em objetos grandes
- Atualizações em tempo real com baixa latência (como um aplicativo de bate-papo)
Isso é importante, já que a implementação de assinaturas dentro do GraphQL não é trivial. Nem só o servidor subjacente precisará ser atualizado, mas o aplicativo de consumo também exigirá algum redesign.
Fortunatamente, o caso de uso que estamos perseguindo com o exemplo do Customer 360 é um ótimo candidato para assinaturas. Também, vamos implementar uma abordagem WebSocket para aproveitar essas assinaturas.
Como antes, eu continuará usando o Apollo.
Credenciais de Aumento de Nível com Assinaturas
Primeiro, precisamos instalar as bibliotecas necessárias para suportar as subscrições com o meu servidor Apollo GraphQL:
npm install ws
npm install graphql-ws @graphql-tools/schema
npm install graphql-subscriptions
Com esses itens instalados, eu fiz foco em atualizar o arquivo index.ts
do meu repositório original para estender a constante typedefs
com o seguinte:
type Subscription {
creditUpdated: Credit
}
Eu também criei uma constante para alojar uma nova instância de PubSub
e criei um exemplo de subscrição que usaremos depois:
const pubsub = new PubSub();
pubsub.publish('CREDIT_BALANCE_UPDATED', {
creditUpdated: {
}
});
Eu limpei as resolvers existentes e adicionei um novo 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']),
}
}
};
Depois, refactorei a configuração do servidor e introduzi o design de subscrição:
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 atualizações dirigidas pelo cliente, eu criei o seguinte método para aumentar o saldo de crédito em $50 a cada cinco segundos enquanto o serviço está rodando. Assim que o saldo alcança (ou ultrapassa) o limite de crédito de $10,000, eu redefino o saldo de volta a $2,500, simulando que uma pagamento de saldo foi feito.
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 arquivo inteiro index.ts
pode ser encontrado aqui.
Deploy para Heroku
Com o serviço pronto, é hora de nós deployar o serviço para que possamos interagir com ele. Já que a Heroku funcionou muito bem a última vez (e é fácil de usar para mim), vamos manter essa abordagem.
Para começar, eu precisava executar os seguintes comandos do CLI 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 também adicionou automaticamente o repositório usado por Heroku como um remoto:
$ git remote
heroku
origin
Como notei em meu artigo anterior, o Apollo Server desabilita o Apollo Explorer em ambientes de produção. Para manter o Apollo Explorer disponível para nossas necessidades, eu precisei definir a variável de ambiente NODE_ENV
como desenvolvimento. Eu defino isso com o seguinte comando CLI:
$ heroku config:set NODE_ENV=development
Setting NODE_ENV and restarting jvc-graphql-server-sub... done, v3
NODE_ENV: development
Eu estava pronto para deployar meu código para o Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku
Uma rápida visualização do painel do Heroku mostrou minha instância do Apollo Server rodando sem problemas:
Na seção Settings, eu encontrei a URL do aplicativo Heroku para esta instância de serviço:
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/
- Observe: Este link já não estará em serviço à hora de publicar este artigo.
Para o momento, eu podia acrescentar graphql
a esta URL para iniciar o Apollo Server Studio. Isto permitiu que eu visse as subscrições funcionando como esperado:
Note as respostas de subscrição no lado direito da tela.
Avançando com as Habilidades de WebSocket
Nós podemos aproveitar a suporte WebSocket e as capacidades do Heroku para criar uma implementação que consome a subscrição que criamos.
No meu caso, eu criei um arquivo index.js com o seguinte conteúdo. Basicamente, isso criou um cliente WebSocket e também estabeleceu um serviço HTTP de exemplo que eu podia usar para validar que o cliente estava rodando:
import { createClient } from "graphql-ws";
import { WebSocket } from "ws";
import http from "http";
// Criar um servidor HTTP de exemplo para se associar a $PORT do Heroku
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);
}
// Subscrever a subscrição creditUpdated
client.subscribe(
{
query,
},
{
next: (data) => handleCreditUpdated(data.data.creditUpdated),
error: (err) => console.error('Subscription error:', err),
complete: () => console.log('Subscription complete'),
}
);
O arquivo completo index.js
pode ser encontrado aqui.
Podemos implantar esta simples aplicação Node.js no Heroku, também, certificando-nos de definir a variável de ambiente GRAPHQL_SUBSCRIPTION_HOST
para a URL da app Heroku usada anteriormente.
Criei também o seguinte Procfile
para informar ao Heroku como iniciar minha app:
web: node src/index.js
Em seguida, criei uma nova app no 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
Então, defini a variável de ambiente GRAPHQL_SUBSCRIPTION_HOST
para apontar para meu servidor GraphQL em execução:
$ heroku --app jvc-websocket-example \
config:set \
GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql
Neste ponto, estamos prontos para implantar nosso código no Heroku:
$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku
Assim que o cliente WebSocket iniciar, podemos ver seu status no Painel do Heroku:
Visualizando os logs no Painel do Heroku para a instância jvc-websocket-example
, podemos ver as várias atualizações na propriedade balance
do serviço jvc-graphql-server-sub
. Em minha demonstração, consegui capturar o caso de uso onde o saldo foi reduzido a zero, simulando que um pagamento foi feito:
No terminal, podemos acessar esses mesmos logs com o 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]: }
Não só temos um serviço GraphQL com uma implementação de inscrição em execução, mas agora temos um cliente WebSocket consumindo essas atualizações.
Conclusão
Meus leitores podem se lembrar de minha declaração de missão pessoal, que acho que pode se aplicar a qualquer profissional de TI:
“Focus your time on delivering features/functionality that extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
— J. Vester
Neste deep-dive no assunto de subscrições GraphQL, nós consumimos atualizações de um servidor Apollo executando no Heroku, usando outro serviço também executando no Heroku — uma aplicação baseada em Node.js que usa WebSockets. Ao aproveitar subscrições leves, nós evitaram enviar consultas para dados que não mudam, mas simplesmente subscrever para receber atualizações de saldo de crédito conforme elas ocorrem.
Na introdução, eu mencionei procurar um princípio de valor adicional dentro de um tópico que eu já escrevi sobre antes. As subscrições GraphQL são um ótimo exemplo do que eu tinha em mente, pois permite que consumidores recebam atualizações imediatamente, sem precisar fazer consultas contra os dados originais. Isto fará os consumidores dos dados do Customer 360 muito animados, sabendo que eles podem receber atualizações em tempo real conforme acontecem.
Heroku é outro exemplo que continua a adherir à minha declaração de missão, oferecendo uma plataforma que permite que eu prototipeie soluções rapidamente usando uma CLI e comandos Git padrão. Isto não só dá-me uma maneira fácil de mostrar o caso de uso de minhas subscrições, mas também implementar um consumidor usando WebSockets.
Se você estiver interessado no código fonte deste artigo, veja meus repositórios no GitLab:
Sinto-me confiante quando digo que dei um upgrade bem-sucedido nas minhas habilidades em GraphQL com esse esforço. Essa jornada foi nova e desafiante para mim – e também muito divertida!
Planejo mergulhar em autenticação a seguir, o que espero oferecer outra oportunidade para upgrade com GraphQL e o Apollo Server. Fique de olho!
Tenha um dia realmente ótimo!
Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions