REST vs GraphQL - Parte 2 - Principais conceitos do GraphQL - Taller
Fala, galera! Na primeira parte desta série de postagens, exploramos alguns cenários hipotéticos bem simples, a fim de demonstrar algumas limitações que a abordagem REST pode apresentar, como por exemplo, servir dados para aplicações rodando em dispositivos diferentes.
Nesta segunda parte o nosso foco será apresentar, de maneira simples e objetiva, os principais conceitos do GraphQL que te sustentarão na terceira parte (a implementação de nossa solução).
Para facilitar os exemplos utilizados e nos mantermos sempre contextualizados com nossa aplicação fictícia, continuaremos utilizando o modelo relacional a seguir como referência:
Para início de conversa vamos esclarecer alguns pontos sobre o GraphQL, mas especificamente, o que ele não é e o que não podemos fazer com ele.
Não, o GraphQL não é uma espécie de banco de dados. Você não conecta o GraphQL ao seu banco de dados. Não é raro haver este tipo de confusão. A associação mais próxima que o GraphQL tem com o banco de dados de sua aplicação é na representação do seu esquema de dados, só que com algumas diferenças. Por exemplo, tome como base a entidade Pessoa do diagrama de classes de nossa aplicação. Com o GraphQL, podemos representá-la assim:
Como você pode ver, existe um elo entre os tipos definidos no seu esquema GraphQL com suas tabelas de banco de dados, no entanto, não existe conexão direta entre estas duas camadas, mas sim, algo meramente conceitual.
Você não baixa o GraphQL.
Não, você não entra no site do GraphQL, obtém um link para download, baixa e o instala em sua máquina. Isso porque o GraphQL é uma especificação e não um software específico. Traçando outro paralelo, você não baixa o SQL, você baixa e instala um SGBD específico, digamos, o MySQL ou o PostgreSQL. Para inserir a camada GraphQL em sua aplicação, você pode utilizar uma das dezenas de implementações que estão disponíveis nas mais variadas linguagens/frameworks (veja aqui).
Linguagens, Sistemas de Gerenciamento de Bancos de Dados.
Outro fato importante que devemos deixar claro: a especificação não faz menção a nenhuma linguagem de programação específica, ou muito menos de algum SGBD. Ou seja, não importa a stack que você escolheu para desenvolver seu projeto, o que importa é haver a implementação da especificação do GraphQL naquela linguagem/framework.
Bem, com estes pontos em mente, vamos avançar e fazer um review dos principais pontos do GraphQL.
Provavelmente um dos conceitos mais fundamentais para se entender o GraphQL. Os Tipos estão por todas as partes (mais detalhes aqui e aqui) . Eles nos permitem compor e dar forma a nosso esquema de dados. Assim, podemos definir quais dados podem ser pesquisados e manipulados pelos clientes de nossa API, entregando-lhes uma espécie de mapa.
Sempre que estivermos definindo algo composto por um nome e por campos, que por sua vez tenham, também, seus tipos, estaremos definindo um Object Type. Sem dúvidas, este será o Type mais comum ao construir API's usando o GraphQL.
Acima, estamos definindo um novo Object Type: Pessoa. A ele, estamos atribuindo 4 fields: id, nome, cpf e data_nascimento. Cada field possui, também, um tipo. Estamos dizendo aos clientes de nossa aplicação: "Bem, nós temos um tipo Pessoa e estes são os campos deste tipo". Você também pode, por exemplo, atribuir a um field um tipo definido por você:
Acima, definimos um tipo Telefone, e no nosso tipo Pessoa, um field telefones que nos possibilita visualizar uma lista de telefones (perceba que o tipo está entre [] por se tratar de uma lista). É bem provável que você tenha percebido um detalhe: Na definição do tipo Telefone nós não inserimos o field Pessoa_id, que está presente no nosso diagrama Entidade-Relacionamento.
Mas, Eric, como faremos esta conexão entre os tipos? Como vou dizer ao GraphQL que um telefone possui o ID de uma pessoa?
Resposta: você não precisa! Sim, não é a definição dos Object Types que irá "cuidar" desse detalhe. Nós mostraremos um pouco adiante daremos este nó =D.
Como mencionamos acima, os tipos são os nossos blocos de construção. Com eles, podemos construir o nosso Schema. Pense no Schema como uma espécie de mapa ou cardápio, que possui os nossos objetos e as conexões entre eles e guiam os clientes de nossa aplicação sobre o que eles podem consumir.
A imagem acima representa o nosso Schema. Ou seja, como desenvolvedor, é este cara que você vai fornecer aos clientes de sua API, para guiá-los sobre o que eles podem consumir de sua aplicação.
Vamos analisar alguns pontos (em relação às imagens anteriores):
Adicionamos mais três Object Types: Query, Email e Endereço.
Adicionamos, ao Object Type Pessoa, mais dois fields: emails e enderecos.
Se você bem percebeu, nós definimos um objeto Query que não possui relação com o nosso diagrama, ou seja, não iremos guardar dados sobre ele em nosso banco de dados. Então, pra quê ele serve?
Primeiro, perceba que ele é composto por um nome e por campos, portanto, é mais um Object Type. Porém, existe algo especial em relação ao nosso Query Type: os campos dele representam quais operações de busca de dados podem ser realizadas no nosso Schema e quais tipos de dados tais operações retornam.
O field pessoas retorna um array de objetos do tipo Pessoa. Já o field pessoa obrigatoriamente recebe como argumento um cpf do tipo String e retorna um objeto Pessoa.
É interessante notarmos que definindo nosso Query Type da forma que está acima, nós não estamos dispondo aos nossos clientes, por exemplo, a possibilidade de listar Telefones, E-mails ou Endereços. Se quiséssemos, digamos, dar aos nossos clientes a possibilidade de listar os e-mails, deveríamos definir mais um field dentro de nosso Query Type:
Agora, vamos analisar um outro Object Type.
Com características similares ao Object Query, o Mutation Type nos permite modificar dados em nossa base. É isso, simples assim.
Você vai definir suas mutações da mesma forma que define suas Querys, nomeando-as, e definindo seus fields:
A operação addPessoa altera nossos dados, e ela recebe, obrigatoriamente um nome e um cpf (campos obrigatórios são marcados com '!'), e você pode passar, se quiser, uma data de nascimento. Após ser executada com sucesso, ela retornará um objeto do tipo Pessoa.
Não há complicações sobre isso, certo?
Clientes recebem de volta exatamente o que eles pedem:
Talvez, um dos aspectos mais legais e um dos argumentos mais fortes que devemos usar quando formos explicar os benefícios do GraphQL: o cliente só recebe o que ele pede. Já pensou pedir uma Pizza, ser intolerante à lactose, e não poder retirar o queijo? Não seria legal, hein? Então, vamos analisar nossos objetos Query e Mutation.
A seguir, um exemplo de uma Mutation e de uma Query:
Na nossa Mutation, caso seja performada de forma correta, teremos como resposta o id e o nome da nova pessoa adicionada.
Já na nossa Query, recebemos de volta, de cada objeto Pessoa os campos nome, cpf e data_nascimento.
Este aspecto é essencial para construirmos APIs que irão servir vários tipos de dispositivos diferentes .
Neste ponto, você não se preocupa apenas com aspectos teóricos. De fato, quando falamos de resolvers nós estamos falando de implementação. Porém. a parte da "mão-na-massa" vamos deixar para a terceira parte desta série, e tentar abstrair ao máximo aqui os tópicos deste post.
No GraphQL, cada field definido em nossos Objetos possuem um Resolver. O resolver – falando de forma bem pragmática – é o cara que soluciona, que resolve o valor daquele field.
Analise a imagem novamente:
Preste atenção no field telefones. Se você voltar ao nosso diagrama, irá perceber uma relação entre as entidades Pessoa e Telefone. Uma pessoa pode possuir vários telefones, e um telefone pertence a uma pessoa, ok?
Onde entram os resolvers?
Bem, como eu disse anteriormente, cada field possui um resolver. E você não precisa escrever todos os resolvers. Por padrão o GraphQL possui um resolver para cada field. Quando você define, por exemplo, o tipo Pessoa e lhe atribui um field chamando nome, o GraphQL busca, na resposta da requisição, um campo do mesmo nome. Caso não haja, retorna null.
Mas, como você sabe, nossa tabela Pessoa no banco de dados não possui um campo Telefones, então, o resolver padrão do GraphQL não irá encontrar o valor para este campo que definimos no nosso Type Pessoa. Para isso, vamos precisar escrever um Resolver, o cara que de fato se comunica com nosso banco de dados e traz esta informação. E como escrevemos isso, você verá na próxima parte desta série, mais precisamente, com JavaScript.
Um único endpoint:
Se você já implementou alguma API Rest, você sabe que nós precisamos definir várias rotas para nossos clientes, por exemplo:
Dizemos, acima, a nossos clientes, o seguinte:
"Se você acessar /nossa-url/pessoa, lhe traremos uma lista de todas as pessoas."
"Se você acessar /nossa-url/pessoa/, e passar um cpf como argumento, lhe traremos uma pessoa relacionada a este cpf".
Com o GraphQL esta complexidade não existe. Você não se preocupa com várias rotas. Todas as tuas consultas ou mutações serão enviadas, como String, para um único endpoint. Mais uma vez, na terceira parte desta série, iremos ver isso com mais detalhes.
Mais um grande facilitador que o GraphQL nos proporciona: A Introspecção. Por definição, todo Schema GraphQL deve ser auto documentável. Ou seja, deve ser possível, aos nossos
clientes, acessar todas as definições de nosso Schema. Para isso, basta enviarmos uma Query __schema para a nossa API:
Isso nos retornará mais ou menos isso:
Massa, né? Esta Query não traz dados guardados sobre os nossos registros, mas, informações sobre o nosso Schema!
Bem, como informamos no início, a intenção aqui era passar, de forma simples e objetiva, uma visão geral sobre os aspectos que irão nos ajudar na implementação da nossa solução.
Recomendo fortemente que você se aprofunde nos tópicos, e leia a documentação oficial.
Aprendemos que: os Types são nossos blocos de construção, feito tijolos. Eles são compostos por um nome e por fields. A maioria deles são Object Types e nos ajudam a construir nosso Schema, que serve de cardápio para os clientes de nossa API, ajudando-os a ter noção sobre o que eles podem consumir em nossa aplicação.
Cada field possui um Resolver. Apesar do GraphQL possuir, por padrão, um resolver para cada field, você pode escrever o seus Resolvers.
Aprendemos, também, que definimos o que os nossos clientes podem consultar no nosso Query Type, e o que eles podem alterar, no nosso Mutation Type. E cada um destes caras retorna exatamente o que o cliente pede.
Na nossa API servida pelo GraphQL, não temos uma lista de endpoints para fornecer aos nosso clientes, mas, um único endpoint.
E, por fim, a introspecção é uma característica do GraphQL que o torna auto documentável. Enviando uma query __schema nós conseguimos navegar por todos os Types de nosso Schema, e todos os seus detalhes.
É isso, gente! Espero, de coração, que a mensagem tenha sido passada com sucesso. Se rolar alguma dúvia, chama aê nos comentários!