Há alguns anos, tenho tentado identificar frameworks, produtos e serviços que permitam aos tecnólogos manter sua foco em estender o valor de sua propriedade intelectual. Essa continua sendo uma viagem maravilhosa para mim, cheia de oportunidades de aprendizado únicas.
O engenheiro em mim recentemente questionou se existia uma 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 original reconhecida anteriormente?
Para esse artigo, eu quis mergulhar mais fundo em GraphQL para ver o que eu conseguiria encontrar.
No meu artigo “When It’s Time to Give REST a Rest“, eu falei sobre como existem cenários reais no mundo que GraphQL é preferível a um serviço RESTful. Nós passamos por como construir e implantar um API GraphQL usando o Apollo Server.
Neste post subsequente, eu planejo aumentar 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 de 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 toda a necessidade de dados 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 múltiplas solicitações e respostas para serem juntadas. O GraphQL nos dá uma solução que performa muito melhor.
Meta de Aumento de Nível
Para avançar em qualquer aspecto da vida, é preciso alcançar novos objetivos. Para minhas próprias metas aqui, isso significa:
- Entender e implementar o valor da proposta de
inscrições
dentro do GraphQL - Usando uma implementação de WebSocket para consumir uma inscrição GraphQL
A ideia de usar inscrições em vez de consultas e mutações no 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, pois a implementação de inscrições no GraphQL não é trivial. Nem só o servidor subjacente precisará ser atualizado, mas o aplicativo de consumo também exigirá algum redesign.
Fortuitamente, o caso de uso que estamos perseguindo com o exemplo do Customer 360 é um ótimo candidato para inscrições. Também, vamos implementar uma abordagem WebSocket para aproveitar essas inscrições.
Como antes, eu continuará usando o Apollo.
Credenciais de Aumento de Nível com Inscrições
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 fui concentrados 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 abrigar 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 driven por clientes, eu criei o seguinte método para aumentar o saldo de crédito em $50 a cada cinco segundos enquanto o serviço está em execução. Assim que o saldo atingir (ou ultrapassar) o limite de crédito de $10,000, eu redefino o saldo de volta a $2,500, simulando que um 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 de index.ts
pode ser encontrado aqui.
Deploy para Heroku
Com o serviço pronto, é hora de deployar o serviço para que possamos interagir com ele. since Heroku worked out great last time (and it’s easy for me to use), let’s stick with that approach.
Para começar, eu precisava executar os seguintes comandos do CLI do 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 remoto:
$ git remote
heroku
origin
Como notei em meu artigo anterior, o Apollo Server desativa 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
para 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 olhada no painel do Heroku mostrou que o meu Apollo Server estava 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 poderia acrescentar graphql
a esta URL para iniciar o Apollo Server Studio. Isto deixou-me ver as subscrições funcionando como esperado:
Note as respostas de Subscrição na parte direita da tela.
Avançando com as Habilidades de WebSocket
Nós podemos aproveitar o suporte a 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 falso que eu poderia 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 falso para se anexar ao $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.
Nós podemos implantar esta aplicação simples em 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.
Também criei 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 à 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 subscrição em execução, mas agora também temos um cliente WebSocket consumindo essas atualizações.
Conclusão
Meus leitores podem se lembrar de minha 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 mergulho profundo nas subscrições GraphQL, nós consumimos atualizações de um servidor Apollo rodando no Heroku usando outro serviço também rodando no Heroku – uma aplicação baseada em Node.js que usa WebSockets. Ao aproveitar as subscrições leves, nós evitaram enviar consultas para dados que não mudam, mas sim se 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 escrevi antes. As subscrições GraphQL são um ótimo exemplo do que eu tinha em mente, porque elas permitem aos consumidores receber atualizações imediatamente, sem precisar fazer consultas contra os dados de origem. Isto fará os consumidores dos dados do Customer 360 muito entusiasmados, sabendo que eles podem receber atualizações ao vivo conforme elas 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 um 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 eu tenho aprimorado minhas habilidades em GraphQL com esforço bem-sucedido. Essa jornada era nova e desafiante para mim – e também muito divertida!
Planejo mergulhar em autenticação a seguir, o que espero oferecer outra oportunidade para aprimorar as minhas habilidades com GraphQL e o Apollo Server. Fique ligado!
Tenha um dia realmente ótimo!
Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions