GraphQL: Conheça a linguagem e suas principais estruturas

GraphQL é uma linguagem criada pelo Facebook em 2015 para facilitar a comunicação com API’s, tanto pela interação sistema-sistema quanto pela humano-sistema – neste último caso há uma grande chance desse humano ser um desenvolvedor.

A linguagem foi desenhada para resolver o problema de underfetch (quando vem menos dados do servidor que o necessário, precisando fazer outra(s) requisição(ões) para trazer os dados desejados) – e overfetch (quando vem mais dados que o necessário do servidor). Como efeito colateral positivo, GraphQL também acaba reduzindo o número de requisições feitas a um servidor, já que é possível trazer todas as informações que uma página precisa em apenas uma requisição, ou, como a gente vai falar de agora em diante, query.

GraphQL não foi desenhada como uma solução para ser usada em apenas uma linguagem, mas uma especificação que pode ser implementada em qualquer linguagem, e é isso que vários projetos open-source tem feito.

Vale ressaltar que GraphQL é uma linguagem fortemente tipada, isto é, cada campo de suas consultas precisa ser de um determinado tipo. Nas próximas seções falaremos mais sobre isso.

REST vs GraphQL

REST

Resumidamente, no padrão REST foi convencionado o uso de rotas e verbos HTTP para definir uma ação. Nessa convenção temos a seguinte configuração de modo geral:

  • get :index – Listagem
  • post :index – Criação
  • get :id – Detalhamento
  • delete :id – Exclusão de um recurso específico
  • put|patch :id – Atualização de um recurso

Todos os recursos no padrão REST são disponibilizados através de um URI. Para ilustrar, imagine que seu sistema possui os seguintes recursos:

  • /users
  • /groups

Através da rota /users é possível pegar informações gerais sobre todos os usuários, e através da rota /groups é possível pegar informações gerais sobre todos os grupos de usuários. Um usuário pode estar em vários grupos e um grupo pode ter vários usuários, caracterizando assim um mapeamento N-M (muitos para muitos) entre users e groups.

Se quiséssemos ter acesso aos usuários de um determinado grupo, a rota /users teria que ter algum filtro para isso, ou ter uma rota aninhada /users sob um recurso /groups/:id.

Exemplo:

  • Rota que exibe os usuários do grupo 42:
    • /users?group_id=42
    • /groups/42/users

O mesmo teria que ser feito caso quiséssemos todos os grupos de um determinado usuário. Exemplo:

  • Rota que exibe os grupos do usuário 10:
    • /groups?user_id=10
    • /users/10/groups

Observação: se você quiser saber mais sobre rotas aninhadas, nesse link o autor discorre sobre os problemas de se ter várias rotas aninhadas.

As rotas acima até seriam bem convenientes mesmo de se ter num sistema, mas eventualmente sua API pode não ter uma relação tão clara entre um recurso e outro, levando desenvolvedores a bombardearem o servidor com requisições.

Exemplo:

function usersPosts(userId) {
 $.get('/users_posts', function(response) {
   if(response.success) {
     response
       .data
       .filter((userPost) => userPost.userId == userId) // arrow function
       .forEach((filteredUserPost) => {
         $.get(`/posts/${filteredUserPost.postId}`, function(response) {
           if(response.success) {
             // tratamento de response.data.post
           }
         });
       });
   }
 });
}


No código acima temos um exemplo clássico que acontece em várias API’s onde o relacionamento entre users e posts não tem uma API adequada para facilitar o fetch de posts de usuários.

Observe quantas requisições estão sendo feitas:

  • 1: pegar os dados no endpoint que informa qual post está relacionado a qual usuário;
  • P: onde P é um número hipotético correspondente ao número de posts de um determinado usuário.

Imagine que a função usersPosts seja chamada para cada usuário do seu sistema. Eventualmente, para 1 cliente acessando, esse código fará U * P requisições, onde U é o número de usuários e P é o número médio de posts por usuário. Imagine que essa sua aplicação é acessada por C clientes. É muita requisição feita a um servidor!

Esse foi o principal fator motivador para o GraphQL no Facebook, pois eles tem bilhões de usuários fazendo muitas requisições, e isso sobrecarregava muito os servidores do Facebook.

GraphQL

Antes de prosseguir, precisamos conhecer alguns conceitos técnicos e básicos de GraphQL:

  • Query: termo usado de para definir qualquer consulta GraphQL que busque (pull)
  • dados no servidor;
  • Mutation: termo usado para definir qualquer consulta GraphQL que envie (push)
  • dados para o servidor;
  • Fields: campos (atributos) definidos para consultas GraphQL;
  • Schema: (esquema)é a definição de todas as queries, mutations,
  • campos e tipos da sua API GraphQL.

Para o exemplo dado na seção anterior, REST, teríamos as seguintes queries GraphQL:

  • Busca todos os usuários e todos os grupos de cada usuário
query {
 users { # users é um campo (field) composto (objeto)
   id # id é um campo (field) simples (String ou Integer ou ID)
   name
   groups { # users é um campo (field) composto tipo Array
     id
     name
   }
 }
}
  • Busca todos os grupos de um determinado usuário
query {
 user(id: 10) {
   name
   groups {
     id
     name
   }
 }
}
  • Busca todos os grupos e todos os usuários de cada grupo
query {
 groups {
   id
   name
   users {
     id
     name
   }
 }
}
  • Busca todos os usuários de um determinado grupo
query {
 group(id: 42) {
   name
   users {
     id
     name
   }
 }
}

Queries e Mutations

Agora que você já viu um pouco da linguagem e um comparativo com o padrão REST, vamos aprofundar um pouco mais na sintaxe e em alguns artifícios que facilitam a manipulação dos dados.

Campos, Tipos e Comentários

Como já vimos anteriormente, campos são os dados que queremos.

Exemplo:

query {
 timeNow
}

Na query acima, uma API GraphQL que implementasse o campo timeNow retornaria a data e hora atual no seguinte formato JSON:

{
 "data": {
   "timeNow": "08/11/2018 19:44:55.978"
 }
}

Nesse caso o campo timeNow da query é um campo do tipo String, bem comum para qualquer linguagem de programação.

Vamos agora para um exemplo mais complexo, quando queremos dados de um usuário:

query {
 user(id: 10) {
   name
   email

   pictures {
     link
     description
   }

   address { # Isto é apenas um comentário
     street
     number
     neighborhood
     city
     state
     country
     zipCode
   }
 }
}

Nesta query, temos o tipo User com os seguintes campos (fields):

  • user: Objeto;
  • name: String;
  • email: String;
  • pictures: Array de objetos do tipo Picture (customizado no backend);
  • address: Objeto;
  • street: String;
  • number: Integer (hipoteticamente. Não use Integer para número de endereço!);

Observe o comentário definido na query. Você colocar comentários nas suas queries para outros desenvolvedores.

O resultado da query acima seria:

{
 "data": {
   "user": {
     "name": "Fulano de Tal",
     "email": "fulano.tal@servidor.com.br",

     "pictures": [
       { "link": "http://www.servidor.com.br/pic1.png", "description": "Uma imagem qualquer" },
       { "link": "http://www.servidor.com.br/pic2.png", "description": "Outra imagem qualquer" }
     ],

     "address": {
       "street": "Rua dos Alfeneiros",
       "number": 4,
       "...": "outros..."
     }
   }
 }
}

Observação: Os “…” no resultado acima não se tratam de um resultado retornado, é apenas uma abreviação que eu fiz para evitar ter que escrever os outros campos. Mas a linguagem GraphQL foi pensada para esses casos onde um objeto complexo possa ter vários campos e o desenvolvedor não precisa ficar escrevendo os mesmos campos sempre. O nome dessa feature de GraphQL é Fragmentos (fragments), que veremos a seguir.

Alias

Alias é um recurso interessante quando você precisa dar um nome diferente a um campo de uma query. Por exemplo, suponha que em sua aplicação você já tenha uma variável com o nome user, no caso da query acima você pode usar um alias para renomear o campo. Exemplo:

query {
 fulanoDeTal: user(id: 10) {
   # ... campos do tipo user
 }
}

Nesse caso a resposta do servidor viria da seguinte forma:

{
 "data": {
   "fulanoDeTal": {
     "name": "Fulano de Tal",
     "age": 30
   }
 }
}

Ainda não foi falado, mas na query acima usamos parâmetros para um campo.

Parâmetros são informações adicionais que você pode fornecer para os campos (fields) de uma query para customizar a consulta. Existem parâmetros que são obrigatórios e parâmetros opcionais, como parâmetros de uma função JavaScript.

O campo user(id: 10), nesse caso, aceita o parâmetro id com um determinado valor. Na query acima queremos pegar dados do usuário com id igual a 10, e damos um alias fulanoDeTal para que o campo user seja renomeado.

Mais abaixo falaremos sobre variáveis, para deixar suas queries mais generalizadas.

Fragmentos

Nos exemplos acima,  foram usados as reticências () para não ter que reescrever os campos.

Nesse caso, poderíamos definir um fragment para a consulta, de forma reutilizável.

Exemplo:

fragment addressFields on Address { # definição do fragmento.
 street
 number
 neighborhood
 city
 state
 country
 zipCode
}

query {
 users {
   id
   name
   ...addressFields # usando o fragmento definido anteriormente.
 }
}

Variáveis e Operação

Apesar de GraphQL não ser uma linguagem de programação, ela traz alguns conceitos dessas linguagens, como parâmetros e variáveis.

Imagine que em sua aplicação o usuário possa gerenciar seus posts (inclusive excluí-los). Para excluir um post, a API espera apenas o id do post a ser deletado.

Nesse caso você pode fazer a seguinte mutation (alteração de dados no servidor):

mutation DeletePost($id: ID!) {   # Definição da variável 'id' do tipo ID
 deletePost(id: $id) {
   deletedAt    # hora da deleção do Post informado através do id
 }
}

A mutation acima define uma variável id do tipo ID!. O sinal de exclamação ! é usado para definir obrigatoriedade da variável. Caso o desenvolvedor não passe a variável id para a mutation acima, isso vai gerar um erro na consulta.

Um outro detalhe importante que vale ressaltar é o Nome da Operação. Em GraphQL é possível dar nomes às queries e mutations, de forma que quando você for executar alguma query você só passe o nome da query que gostaria que fosse executada. No exemplo acima dei o nome DeletePost para a mutation.

Há vários outros recursos menos utilizados que você pode encontrar na página oficial do projeto GraphQL para caso queira saber mais sobre eles.

Conclusão

O GraphQL já está no mercado há mais de dois anos e vem ganhando cada vez mais força devido a sua facilidade e os benefícios que traz tanto aos desenvolvedores quanto às aplicações, melhorando também questões de desempenho, uma vez que é possível fazer um uso mais inteligente dos recursos dos servidores. Recentemente a Linux Foundation, uma importante organização no mundo open source, anunciou que focará esforços para fomentar e suportar o ecossistema de GraphQL.

Enfim, a GraphQL pode ser empregada em diversos casos. Bancos de dados modernos podem adicionar uma interface de consulta GraphQL além da interface SQL tradicional, além de várias outras aplicações.

No próximo post falarei mais sobre GraphQL no backend e como configurar sua API para que ela trate requisições GraphQL.

Gostou do artigo? Compartilhe em suas Redes Sociais!