Eu já estava há um bom tempo para falar sobre GraphQL. A ideia é mostrar, da forma mais real possível, a utilização desta tecnologia.

 

Ao longo da criação do projeto de exemplo, acabei incluindo coisas muito bacanas, que se encaixam muito bem nos projetos reais, portanto espere encontrar bons exemplos com as seguintes tecnologias:

  • Bogus;
  • HotChocolate;
  • Entity Framework Core;
  • Injeção de Dependência;
  • Paginação com GraphQL;
  • Queries;
  • Mutations.

(o código fonte estará disponível no GitHub, cujo link está no final deste artigo)

 

Um pouco da história do GraphQL

Antes de mais, o que é o GraphQL?


“GraphQL é um idioma de consulta para API’s e um tempo de execução para cumprir essas consultas com seus dados existentes. O GraphQL fornece uma descrição completa e compreensível dos dados em sua API, dá aos clientes o poder de pedir exatamente o que precisam e nada mais, facilita a evolução das API’s ao longo do tempo e permite criar poderosas ferramentas de desenvolvedor.”

 

Esta é a definição formal de GraphQL, que inclusive está no site oficial, mas voltemos um pouco atrás no tempo. Foi em 2012 que surgiu o GraphQL, a partir de um projeto interno do Facebook, mas foi de fato lançado publicamente em 2015. 


Já em 2018 foi finalmente transferido para a GraphQL Foundation, uma organização Linux sem fins lucrativos. Seu criador, Lee Byron, afirma que o produto tem seguido seu objeto desde o início e que é ser omnipresente em todas as plataformas web. 


O projeto aconteceu, na verdade, porque a Apple concentrou todos os seus recursos em aplicativos móveis nativos, enquanto o Google não se preocupava muito com a experiência do utilizador na app web/mobile. Nesse mesmo ano, o Facebook construiu seu aplicativo, apostando muito em HTML5 (grande erro, segundo Mark Zuckerberg), mas a transição do mercado web para mobile não estava madura o suficiente. Então, o Facebook contratou muitos engenheiros séniores de iOS e, com isso, decidiu reescrever o aplicativo todo para iOS, começando pelo feed de notícias.


Como padrão utilizaram uma API RESTful e isso trouxe diversos problemas:

  • Lentidão na rede: a API não dava conta de retornar todos os dados, então o cliente era forçado a fazer mais e mais solicitações para diferentes API;
  • Problema de documentações: por conta da complexidade e quantidade de implementações, a documentação por vezes ficava desatualizada;
  • Manter a aplicação: sobre as respostas de API, caso a API mudasse seu formato de resposta, o código do cliente deveria mudar em conformidade. Os engenheiros tiveram de manter manualmente as classes de modelos do cliente, a lógica da rede e outras coisas para garantir que os dados certos eram carregados no momento certo antes de renderizar a exibição.

 

Vantagens e desvantagens?

Mas o que realmente é o GraphQL? Quais são suas vantagens e desvantagens?


A resposta é muito rápida: o GraphQL é uma linguagem de consulta usada para buscar apenas os dados que o cliente deseja do banco de dados.

 

Vantagens:

  • Rápido na rede: ao contrário da abordagem RESTful, o GraphQL traz apenas o necessário para o cliente. Isso diminui a quantidade de chamadas e o tamanho das requisições;
  • Tipos estáticos robustos: permite que os clientes saibam quais dados estão disponíveis e que tipo de dados são;
  • Capacitando a evolução do cliente: o formato da resposta é controlado inteiramente pela consulta do cliente. Assim, o código do lado do servidor torna-se muito mais simples e fácil de manter. Quando você remove campos antigos de uma API, esses campos serão preteridos, mas ainda assim continuarão funcionando. Este processo gradual de compatibilidade retrógrada remove a necessidade de versionamento. Depois de tantos anos, o Facebook ainda está na versão 1 de sua API GraphQL;
  • Documentação: a documentação é gerada automaticamente e sempre atualizada;
  • Código arbitrário de consulta: o GraphQL é adaptável. Não se trata de bancos de dados. Ele pode ser adotado em cima de uma API RESTful existente e pode trabalhar com ferramentas de gerenciamento de API existentes.

 

Desvantagens:

  • Curva de aprendizado: se você já conhece o REST, possivelmente pode ter algumas dificuldades em sua implementação;
  • Armazenamento em cache: é mais difícil com GraphQL;
  • Consultas: sempre retornam o código 200.

 

Chega de falar! Mãos à obra!

  1. Vamos criar um projeto de API:

    GraphQL 1
  2. Selecione o tipo de projeto “ASP.NET Core Web API”:

    GraphQL 2
  3. Informe um nome para sua solução:

    GraphQL 3
  4. Selecione a versão “.NET 6.0 LTS”, mantenha demais opções como mostra a imagem e clique em “Criar”:

    GraphQL 4
  5. A estrutura da solução deverá ficar desta forma:

    GraphQL 5
  6. Crie as seguintes pastas:

    GraphQL 6

    - “Data” é onde criaremos as classes que vão popular nosso banco de dados SQL Server (seeds). Esta pasta também deverá ter a classe de contexto;
    - “GraphQL” é onde estão os arquivos de “Queries” e “Mutations”, que mais tarde abordaremos;
    - “Interfaces”, como o nome já sugere, é onde criaremos uma interface para tratar dos nossos serviços;
    - “Migrations” é criada automaticamente quando corremos a migração;
    - “Models” é onde devemos criar nossas classes concretas dos modelos;
    - “Services” deverá conter a classe concreta de serviço, que deve implementar a interface anteriormente criada;
    - “ViewModels” é apenas uma classe de transporte.


  7. Agora vamos instalar os pacotes necessários para este projeto:
    - Install-Package Bogus
    - Install-Package HotChocolate.AspNetCore
    - Install-Package HotChocolate.Data.EntityFramework
    - Install-Package Microsoft.EntityFrameworkCore
    - Install-Package Microsoft.EntityFrameworkCore.SqlServer
    - Install-Package Microsoft.EntityFrameworkCore.Tools
    - Install-Package Microsoft.EntityFrameworkCore.Abstractions
    - Install-Package Microsoft.EntityFrameworkCore.Design
    - Install-Package Microsoft.EntityFrameworkCore.Relational

  8. Criamos agora as classes “Customer” e “Invoice” na pasta “Models”.

    Primeiro vamos criar as duas classes que representarão nosso modelo. O objetivo é ter uma classe chamada “Customer” e outra “Invoice”. A última é uma relação de faturas por cliente (simularemos vários clientes), que por sua vez possuem uma sequência aleatória de faturas. Isso será criado automaticamente utilizando alguns ótimos recursos (chegaremos lá).


    Classe Customer
    Crie primeiro uma interface na pasta “Interfaces” chamada “IAggregateRoot”:

    GraphQL 7

    Temos dois construtores na classe “Customer” – o primeiro é utilizado meramente para o Entity Framework.

    Podemos ver também que a classe faz referência a “Invoice”, que criaremos na próxima etapa. Somente com “Customer” será mostrado um erro, até que a classe “Invoice” seja criada.

    Fiz uso de algumas técnicas importantes:

    - Vejam que a classe “Customer” implementa uma interface chamada “IAggregateRoot”, onde sua única função é ser uma interface de marcação. É uma boa prática e muito aplicada em projetos que utilizam o design DDD. Serve para mostrar que “Customer” é uma raiz de agregação. Essa técnica garante coesão, expressividade e integridade do domínio, uma vez que o acesso a essas entidades só pode ser feito a partir da entidade raiz, que no nosso caso é “Customer”. Importante levar em consideração que é apenas uma demonstração – na prática, esta técnica aplica-se a projetos maiores que possuem mais contextos;
    - Outra importante característica é a configuração das propriedades da classe, decorando as mesmas com validações. Fazendo uso de Data Annotations conseguimos diretamente na classe criar validações que vão nos ajudar a definir quais campos são obrigatórios, ranges, tamanho de campos, comparações de datas, regex e muito mais;
    - Todos os nossos “set’s” são privados, escondidos do mundo externo, dando a responsabilidade à própria classe para a criação de uma nova instância. Desta forma, garantimos o estado da nossa classe e somente será possível alterar a partir dos métodos providos por ela.


    Aproveitei para indicar a chave primária e a relação que teremos de 1: N com a classe “Invoice”. Eu particularmente não crio desta forma. Em projetos maiores faz sentido criar as classes de mapeamento devidamente separadas - além do mais, a classe concreta fica com o código mais limpo e fácil de entender.

    GraphQL 8

    Classe Invoice
    Muito parecida com a classe “Customer” em relação à estrutura. As linhas 35, 36 e 37 referenciam a classe “Customer” para efeitos de criação das tabelas e seus relacionamentos utilizados no Entity Framework.

    GraphQL 9
  9. Crie a classe “CustomerDbContext” dentro da pasta “Data”. Ela vai herdar de “DbContext”, que faz parte do Entity Framework. Basicamente, ela representa um tipo de sessão com o banco de dados e, desta forma, nos permite fazer consultas, salvar instâncias, etc....

    GraphQL 10
  10. Vamos criar dados automáticos com Bogus. Um recurso que uso muito em projetos é o pacote “Bogus”. Ele nos ajuda a criar dados fictícios de forma muito inteligente e eficiente, e possui uma série de métodos que atendem a todo o tipo de geração de dados, como por exemplo dados financeiros, datas, dados humanos como data de nascimento, gênero, nomes, dados matemáticos, e por aí fora…

    Crie uma pasta dentro da pasta “Data” e chame ela de “Seeders”. Dentro desta última pasta, crie uma classe chamada “DbInitializer”.

    Veja que o método “Seed” vai criar uma instância de “Customer”. Na linha 23 informamos quantos registos pretendemos criar e, de forma aleatória, ele vai criar para você uma série de “Customers”. “Invoices” funciona da mesma forma, com um detalhe importante: na linha 35, o método “PickRandom” vai obter um “Customer” já criado para relacionar as “Invoices”.

    Essa é uma ótima ferramenta para usar em testes unitários.


    GraphQL 11

    Agora voltamos na classe “CustomerDbContext” e incluímos a chamada para a classe “DbInitializer”, na sobrecarga do método “OnModelCreating”, que é executado toda vez que fazemos o update-database de uma migração.

    GraphQL 12
  11. Registe as funcionalidades na API. Abra a classe “Program.cs” e inclua estas linhas. Aqui estamos configurando nosso DbContext na aplicação. Mais tarde vamos voltar a alterar esta classe:

    GraphQL 13

    Perceba que a linha 7 faz referência a string de conexão, que será utilizada para persistência de dados, portanto abra o arquivo “appsettings.json” e registe a sua.

    GraphQL 14
  12. Adicione a migração: crie banco de dados + tabelas. Se tudo correu bem até agora, este é o momento certo para correr dois comandos: o primeiro vai criar a migração com base nas “Models” e o seguinte vai de fato executar os comandos SQL para nosso banco de dados. 

    Portanto, no Package Manage Console faça correr o primeiro comando:
    1 – Comando principal;
    2 – O nome que identifica a migração (no meu caso utilizei “initial”);
    3 – Parâmetro que identifica o contexto;
    4 – Nome do contexto;
    5 – Significa “Verbose”, não obrigatório (eu sempre utilizo, pois o processo todo será mais detalhado no output).


    GraphQL 16

    Se correu bem deverá ver um arquivo parecido com este. É gerado automaticamente a cada migração e contém todos comandos necessários para criação/alteração do nosso banco de dados e respetivas tabelas, índices, etc.…

    GraphQL 17

    O segundo comando é o que vai de fato executar os comandos na base de dados.
    1 – Comando principal;
    2 – Parâmetro que identifica o contexto;
    3 – Nome do contexto;
    4 – Significa “Verbose”, não obrigatório (eu sempre utilizo, pois o processo todo será mais detalhado no output).

    GraphQL 18

    Este último é muito importante, pois é neste momento que a classe “DbInitializer” será chamada para criar os nossos dados “fakes”. Tudo isso automaticamente. Abra seu gerenciador de SQL Server e veja se tudo foi criado corretamente.

    Podemos ver que foram criadas 3 tabelas: a primeira da imagem é de controle do próprio Entity Framework; a segunda é a “Customers”; e a terceira é a “Invoices”.

    GraphQL 19

    Os dados já foram criados conforme imagem:

    GraphQL 20
  13. Seguindo para a criação de serviços. Vamos agora criar uma interface “ICustomerService”, que deverá conter os métodos para persistência dos nossos dados:

    GraphQL 21

    Na sequência vamos criar o serviço que deverá implementar esta interface. Primeira parte: implementamos os métodos para incluir “Customer” e “Invoice”:

    GraphQL 22

    Depois os métodos de obter, editar e deletar dados:

    GraphQL 23
  14. Registe os serviços em “Program.cs”:

    GraphQL 24
  15. Agora finalmente vamos ao tema GraphQL. Existem duas operações primárias no GraphQL:
    - Query: Serve para pesquisas de dados
    - Mutation: Serve para alterações de dados


    Query
    Na prática, a Query funciona de forma fluída. Você vai informar os campos que pretende utilizar, mas sem a necessidade de ter no servidor uma view model para cada serviço que pretende consumir e retornar valores.

    Ex.: A esquerda mostra um tipo de consulta onde, por mais que o serviço possa retornar 10 campos, eu informo somente os que de fato vou precisar. À direita, a lista de “pets” somente com os campos que solicitei.


    GraphQL 25

    Este outro exemplo mostra como podemos buscar um dado pelo código:

    GraphQL 26

    Mutations
    As mutações, como o nome sugere, é responsável por alterações de dados. Sempre que quisermos editar, incluir ou deletar um dado, vamos utilizar este tipo.

    Ex.: Informamos que queremos utilizar uma mutation, depois basta adicionar o método previamente configurado e o input de valores. Isso já bastaria para fazer a inclusão deste dado:

    GraphQL 27
  16. Criando nossas queries: crie uma pasta chamada “Queries” dentro da pasta “GraphQL”. Logo em seguida crie uma classe chamada “CustomerQuery”, devendo ficar assim:

    GraphQL 28

    Primeiro vamos fazer a injeção de dependência do serviço que retorna os dados. Depois crie 3 métodos, sendo o primeiro para retornar todos os “Customers”, o segundo deverá obter um “Customer” por Id, e o terceiro vamos retornar “Customers” informando um nome.

    GraphQL 29
  17. Agora precisamos registar o GraphQL em “Program.cs”:

    GraphQL 30
  18. Altere a configuração do projeto, nas propriedades, clicando com botão direito no projeto, última opção “Properties”, e busque por “env”.

    Desta forma, a abertura será a partir da página:

    GraphQL 31

    E execute sua aplicação. Deverá ter uma resposta como esta:

    GraphQL 32
  19. Vamos a nossa primeira consulta. Clique em “Create Document” e escreva a seguinte Query…

    GraphQL 33

    E voilà! Temos nossa primeira Query com sucesso!

    Algumas características importantes:
    - Podemos verificar que eu informo “query” e logo na sequência eu posso dar um nome para esta query. No meu caso “FindAll”, digo que quero somente “id”, “name” e “createdAt”. Com “Invoices” a mesma coisa.
    - Na lateral direita já conseguimos ver o resultado, a lista de “Customers” e suas respetivas “Invoices”.

    GraphQL 34
  20. Agora podemos pesquisar por “Nome”. Exemplo:

    Pesquisando somente “Customers” que contenham no nome “Carlos”, primeira imagem é a pesquisa e logo abaixo podemos ver o método que foi utilizado no server. Veja que agora estou trazendo somente 3 campos de cliente:

    GraphQL 35

    GraphQL 36
  21. Vamos agora pesquisar por Id. Obtenha um Id aleatório na tabela “Customer”. Veja que trouxe somente dados referente ao “Customer” com o Id = “44C45E62-9CA5-023B-9DC4-26BDDC0231F7”:

    GraphQL 37
  22. Agora uma dica valiosa: vamos implementar uma paginação para conseguir visualizar nossos “Customers”. Crie um método conforme abaixo:

    GraphQL 38

    Decoramos nosso método primeiro com o atributo que nos ajuda aceder o contexto para o GraphQL. Logo em seguida, mais um atributo que configura o uso de paginação.

    Existem 2 tipo de paginação:
    1) UsePaging: middleware retorna um cursor que deve ser utilizado posteriormente para cada chamada. Desta forma, ele segue para as próximas páginas;
    2) UseOffsetPaging: modelo mais tradicional, onde são informados e retornados metadados contendo número da página, tamanho da página, entre outros que veremos.

    Execute a aplicação e faça uma nova query desta forma:

    GraphQL 39

    Observe na sequência:
    1 – Informe os parâmetros “skip” e “take” que, respetivamente, representam a página e o número de itens por página;
    2 – O “PageInfo” é fornecido pelo GraphQL;
    3 – “Items” é a lista de itens retornados com base nos parâmetros informados;
    4 – O retorno com “PageInfo” preenchido;
    5 – No console podemos ver a query que foi executada no SQL Server. Retorna o que precisamos? Sim. Está correto? Não.


    Ainda temos um problema: imagina uma tabela com 50 mil itens. Mesmo implementando a paginação, a mesma só é aplicada depois que a consulta já foi feita na base de dados. Portanto da forma como está implementado, para cada requisição, todas as vezes será obtido 50 mil registos para depois paginar. Isso com certeza trará grande dor de cabeça com relação a performance. Isso ocorre porque utilizamos um “IEnumerable” como forma de pesquisa, ou seja, primeiro a consulta é feita na base de dados, e posteriormente filtrada em memória.

  23. Resolvendo o problema da paginação: altere o método para que fique assim:

    GraphQL 40

    Veja que passou a retornar um “IQueryable”, que faz a consulta à base de dados já passando filtros/parâmetros de paginação, trazendo de fato somente os dados que precisamos.

    O resultado é o mesmo, só que podemos ver que o comando no SQL Server já recebe os parâmetros diretamente na query.

    GraphQL 41
  24. Vamos criar as nossas mutações. Relembrando, as “mutations” são utilizadas quando precisamos alterar o estado das nossas entidades, seja para edição/inclusão/remoção.

    A primeira coisa é criar uma pasta chamada “Mutations” dentro da pasta “GraphQL”. Dentro da nova pasta criamos a classe “CustomerMutations.cs”.

    GraphQL 42

    Dentro da pasta “ViewModels” devemos criar duas View Models: “CustomerViewModel” e “InvoiceViewModel”, utilizado para receber os parâmetros das requests.

    GraphQL 43
    GraphQL 44

    A classe “CustomerMutation”, assim como a Query, vai conter o código responsável por consumir o serviço “CustomerService”.

    Então podemos ver que possuímos aqui os métodos “AddCustomerAsync”, “UpdateCustomerAsync” e “DeleteCustomerAsync”.


    GraphQL 45
  25. Vamos registar a mutation. Novamente no “Program.cs” vamos incluir o seguinte código. Altere seu AddGrapQLServer para conter a sua Mutation:

    GraphQL 46
  26. Adicionar um registo:

    GraphQL 47
  27. Incluir uma Invoice:

    GraphQL 48
  28. Alterar um registo:

    GraphQL 49
  29. Remover um registo:

    GraphQL 50

 

Conclusão

GraphQL é uma ótima opção para construção de API. Realmente o desempenho é ótimo, temos menos arquivos no projeto, tendo em vista que não precisamos criar vários endpoints. Permite que o cliente (Mobile, Web, Angular, React) tenha mais liberdade quanto ao uso e tratamento de queries, e não fique tão dependente do lado de servidor.


Entender o tipo de projeto e se seu time está preparado para performar com GraphQL como faz com REST também é um fator de atenção.

 

Links úteis:

 

Recursos:

Partilha este artigo