Entendendo conexões em Relay e GraphQL
Entenda com ilustrações o conceito de conexões
Olá, esse é o meu primeiro artigo para a comunidade dev. Espero que aproveitem e seja esclarecedor o que tenho a passar.
Este artigo é uma versão escrita de uma das vídeo aulas da minha série sobre desenvolvimento Fullstack utilizando Node.js, React, React Native com GraphQL e Relay.
Nele vamos explorar o que é o GraphQL e o Relay e como eles juntos implementam o conceito de conexões que oferece uma solução melhor para paginação de resultados em comparação com a paginação baseada em janelas (offset)
O que é GraphQL?
Para resumo, GraphQL é uma linguagem de requisição de API, que permite maior flexibilidade para o consumidor da API pedir em apenas uma requisição todo o dado necessário para mostrar na página, para uma descrição mais detalhada veja este artigo.
O que é o Relay
O Relay é uma biblioteca de código aberto que visa facilitar o uso de GraphQL para o desenvolvimento de aplicações orientado a dados com o React. Para tanto, ela estabelece padrões de modelagem do schema GraphQL e boas práticas de utilização da API. Quem pode explicar melhor sobre isso é o Sibelius Seraphini nesta apresentação.
Problemas com a paginação baseada e “limit” e “offset”
Vejamos um caso simples de paginação. Um item contém outros 4 itens conectados a ele como na figura 1, que mostra também o usuário requisitando a primeira página, sendo que cada pagina contém 2 itens.
Em seguida, o usuário requisita a página 2 e o dado é retornado de forma normal.
Caso de adição de item
Avançando página
Um problema acontece se um novo item é adicionado logo após o item 1 enquanto ele ainda está vendo a página 1, ou seja, todos os itens a partir do item 2 trocam de posição na lista. Assim, quando o usuário mover para a página 2, o item 2 é visto novamente, assim como mostra a Figura 3.
Para o usuário final esta situação será visto como um item duplicado na lista.
Voltando a página
Também acontece uma outra situação com o movimento reverso, ou seja, a partir da página 2 (figura 2) o usuário está vendo os itens 2 e 3, ao se mover para a página 1, por conta da adição do item 5, ele verá os itens 1 e 5, não vendo o item 2 durante esta paginação.
Caso de remoção de item
De forma semelhante, o mesmo problema acontece com a remoção de item no meio da lista, que pode ser uma deleção de um comentário ou um post.
Avançando página
Partindo da página 1 (figura 1), se o item 2 for deletado, ao avançar para a página 2 o item 3 não é visto pelo usuário, assim como mostra a figura 5.
Voltando a página
Partindo da página 2 (figura 2), se o item 2 for deletado, ao voltar para a página 1 o item 3 é visto duas vezes.
O que são conexões em Relay?
Conexões são relações entre um item com vários outros itens, ou seja, representa uma relação 1:n. Por exemplo, um post tem uma conexão com vários comentários, que podem ter conexão com outros comentários. O acesso à estes itens conectados é paginado seguindo um padrão baseado em cursor. Como o Relay foi criado pelo Facebook, este padrão foi desenvolvido pensando em “feeds”, ou seja uma lista com dados frequentemente sendo inseridos ou deletados em qualquer posição da lista.
Pense, por exemplo na lista de comentários. A qualquer momento uma pessoa pode deletar ou editar um comentário, assim como a qualquer momento uma outra pessoa pode adicionar mais um comentário no inicio ou começo da lista, ou ainda responder a comentários. Nestes cenários a paginação tradicional, ou seja, por meio de “limit” e “offset” apresenta alguns problemas.
Paginação baseada em cursor
Por conta dos erros descritos acima que a paginação baseada em cursor foi desenvolvida.
Ela consiste em um nó (node em inglês) conectado a outros nós por meio de arestas (edges em inglês). As arestas contém uma informação importante para a paginação em si, que é o cursor. Um cursor é uma string que não deve ser apenas único para cada nó da conexão, mas também armazena informação para ordenação da lista.
Ao invés de acessar página 1, ou página 2, a requisição pede os primeiros itens, podendo ser após um cursor ou os últimos itens, podendo ser antes de um cursor.
Caso de adição de item
Por conta de ser baseado em cursor, uma adição de item no começo da lista não afeta a paginação, vejamos os exemplos.
Avançando na lista
Partindo do inicio (primeiros 2 itens), a próxima requisição é feita da forma “primeiros 2 itens depois do cursor b”, assim, mesmo se entrar um novo item entre os itens 1 e 2 com um cursor “aa”, os itens 3 e 4 serão vistos e o item 2 não será repetido na paginação.
Voltando na lista
Partindo do final da lista (últimos 2 itens) a próxima requisição é feita da forma “últimos 2 itens antes de c”, o que irá retornar os itens 2 e 5, como é o esperado.
Caso de deleção de item
Avançando na lista
Partindo do inicio (primeiros 2 itens), mesmo se o item 2 for deletado, o cliente ainda tem a informação de que o último cursor recebido foi o cursor b, então a requisição ainda vai ser “primeiros 2 depois do cursor b”, logo os itens 3 e 4 são retornados, como desejado.
Voltando na lista
Partindo do fim (últimos 2 itens), o cliente tem a informação o cursor inicial é o cursor “c”. Caso o item 2 for deletado a requisição continua sendo da forma “últimos 2 antes do cursor c”, retornando apenas o item 1 e não repetindo o item 3.
Observação especial
A informação de itens totais na conexão não faz parte da especificação, pois como são de listas infinitas, a operação de contagem de itens pode acabar não tendo sentido para alguns casos, portanto, optou-se por não adicionar na especificação. Isso significa que, caso houver o contador de itens totais, toda operação de inserção e adição na conexão deve atualizá-lo de forma “manual”, seja buscando a informação no servidor junto com a conexão ou atualizando de forma direta na store do Relay com commitLocalUpdate.
Conclusão
A paginação baseada em cursor adiciona mais conceitos do que uma paginação tradicional, mas pode resolver problemas comuns da paginação. É complexo no começo, mas com o tempo se torna natural pensar em conexões quando se trata de listas. Um exemplo de utilização de conexão baseada em cursor é a API do github onde todo tipo de paginação é feita por meio de cursores. Outro exemplo de paginação baseada em cursor é a utilizada nas notificações do Facebook, se você abrir as ferramentas de desenvolvedor na aba “Network” e procurar as chamada para o endpoint/graphql
ao abrir a lista de notificações, não conseguirá identificar a query porque é criptografada, mas poderá identificar nas variáveis da query e a resposta recebida, que será no formato de arestas ("edges") com cursores e nós ("node")