GraphQL subscriptions: como funcionam na conexão | We.Inc
O que são GraphQL subscriptions, a regra de um só campo raiz, um passo a passo pelas mensagens do graphql-ws, a opção com SSE e quando o polling é a melhor escolha.
Uma GraphQL subscription é uma operação de longa duração: o cliente envia um documento de subscription uma vez, e o servidor envia um novo resultado sempre que o evento correspondente acontece. O GraphQL não define o transporte. Na prática, ela roda sobre WebSockets, com o protocolo graphql-ws, ou sobre Server-Sent Events. Cada operação de subscription precisa selecionar exatamente um campo raiz.
Subscriptions, polling e nova busca
Abordagem
Como as atualizações chegam
Bom para
Subscription
O servidor envia um resultado por evento
Mudanças pequenas e frequentes que o usuário espera ver na hora
Polling
O cliente repete uma query em um intervalo
Dados que mudam de vez em quando
Nova busca após uma ação
O cliente consulta de novo depois de um passo do usuário
Telas que só mudam quando o usuário age
Notificação push
Uma mensagem fora do app
Usuários de celular com o app fechado
O passo a passo abaixo reproduz uma subscription sobre o protocolo graphql-ws, mensagem por mensagem. Cada passo mostra o frame JSON que cruza o socket e qual lado o envia, do primeiro handshake até o complete final.
Clique em Próximo para avançar na conversa um frame por vez.
Veja quem envia cada frame e o que ele significa.
Observe o id da operação: todo frame de uma mesma subscription carrega o mesmo id.
Use Voltar ou Recomeçar para ver um passo de novo.
Comece com polling e mude para uma subscription só quando alguns segundos de atraso realmente prejudicarem a experiência.
Envie um evento pequeno em cada resultado, como a nova mensagem, em vez da lista inteira, e deixe o cliente fazer a junção.
Planeje as reconexões: depois que o socket cai, o cliente precisa assinar de novo e buscar o que perdeu.
Verifique a autenticação quando a conexão abre e de novo dentro de cada resolver, já que as permissões de um usuário podem mudar durante uma sessão longa.
Perguntas frequentes
Por que só um campo raiz por subscription?
Cada subscription corresponde a um fluxo de eventos. O graphql.org mostra que colocar dois campos raiz em uma operação de subscription é inválido; escreva duas subscriptions nomeadas.
Qual subprotocolo WebSocket o graphql-ws usa?
Ele negocia o graphql-transport-ws durante o handshake, com tipos de frame como connection_init, subscribe, next e complete. Outras bibliotecas GraphQL para WebSocket podem usar um protocolo diferente, então confirme que cliente e servidor concordam antes de depurar qualquer outra coisa.
As subscriptions funcionam sem WebSockets?
Sim. O protocolo graphql-sse envia cada resultado como um Server-Sent Event chamado next e termina com complete. Ele funciona sobre HTTP comum, o que ajuda atrás de proxies que bloqueiam upgrades de WebSocket.
As subscriptions escalam como as queries?
Não. O graphql.org observa que cada cliente assinante fica preso a uma instância do servidor e precisa de estado mantido durante toda a sua vida, então você também precisa de um sistema pub/sub para distribuir os eventos entre servidores.
O que acontece quando você assina
O cliente envia um documento que começa com a palavra-chave subscription, escolhe um campo raiz e lista os campos que quer em cada evento. O servidor o valida uma vez e o mantém aberto.
Quando algo publica um evento correspondente, como uma nova avaliação, o servidor executa o selection set sobre esse evento e envia o resultado. Cada resultado tem o formato normal de data e errors, então o código do cliente o lê do mesmo jeito que lê uma query.
O protocolo graphql-ws em resumo
Depois do upgrade do WebSocket, o cliente envia connection_init, muitas vezes com um token de autenticação no payload, e espera o connection_ack. Só então pode enviar frames subscribe. Cada subscribe leva um id único, e todo frame next, error ou complete daquela operação o repete, para que muitas subscriptions compartilhem um único socket.
Qualquer lado pode enviar ping e o outro responde pong. O servidor fecha o socket com o código 4408 se o connection_init não chegar a tempo, e com 4409 quando um cliente reutiliza um id que ainda está em execução.
Quando o polling é a melhor escolha
Subscriptions precisam de conexões persistentes, de uma camada pub/sub e de código cuidadoso no cliente para reassinar e lidar com condições de corrida. Para um dashboard que atualiza a cada minuto, uma query em um intervalo é mais simples e falha de forma mais tranquila.
O graphql.org sugere subscriptions para dados que mudam com frequência e em pequenos passos, e polling, notificações push ou nova busca para dados mais lentos ou clientes móveis com pouca banda.
Schema e código do cliente
Declarando um campo de subscription
Subscription é um tipo raiz, como Query e Mutation. Cada campo é um fluxo que um cliente pode abrir.
Assinando com o cliente graphql-ws
createClient abre o socket só quando necessário, e iterate retorna um iterador assíncrono que entrega um resultado por evento.
A regra de um só campo raiz
Separe dois fluxos em duas operações
Uma única subscription que lista reviewCreated e outro campo raiz falha na validação. Duas operações nomeadas funcionam.
O cliente envia connection_init
Enviado logo depois que o socket abre. O payload é livre; os servidores costumam ler um token dele.
O servidor envia connection_ack
O servidor aceitou a conexão. Antes disso, um frame subscribe fecharia o socket com 4401 Unauthorized.
O cliente envia subscribe
O id é escolhido pelo cliente e precisa ser único entre as operações em execução. O payload é a query e as variáveis de sempre.
O servidor envia next
Uma avaliação foi publicada, então o servidor executou o selection set e enviou o resultado com o mesmo id.
O servidor envia next de novo
Outro evento, outro frame. Isso se repete enquanto a subscription continuar aberta.
O cliente envia complete
O usuário saiu da página, então o cliente encerra a operação. O socket pode continuar aberto para outras subscriptions.