GraphQL em .NET, uma alternativa ao REST

Matheus Ibrahim Fonseca Magalhaes
#LocalizaLabs
Published in
10 min readAug 12, 2022

Quando desenvolvedores precisam desenvolver uma API, muitas vezes já assumimos que devemos usar REST, porque é assim que a maioria de nós já está acostumado. Por isso, nem refletimos se essa realmente é a melhor solução para a API que precisamos criar.

REST foi um grande marco quando se trata de comunicação entre APIs, e até hoje é o padrão mais usado para o desenvolvimento de APIs. Mas isso não quer dizer que essa é a única opção, nem que é a melhor opção para todos os cenários.

Nesse artigo, vou apresentar uma dessas alternativas, o GraphQL. O objetivo aqui é introduzir o que é o GraphQL, quais as vantagens e desvantagens dele com relação ao REST, e apresentar uma API básica utilizando essa tecnologia.

O que é GraphQL

Foi desenvolvido originalmente pelo Facebook em 2012, e se tornou Open Source em 2015. Desde 2019, é mantido pela GraphQL Foundation. Originalmente, foi desenvolvido em JavaScript, que ainda é a linguagem mais frequentemente usada com GraphQL, mas hoje já possui suporte para 23 linguagens de programação diferentes.

GraphQL é uma Query Language para APIs que é utilizada para executar queries em tempo de execução no lado do servidor, utilizando um type system definido pelo próprio sistema, com o objetivo de criar APIs rápidas e flexíveis que permitam facilitar tanto o desenvolvimento do programador quanto o entendimento por parte do cliente.

Vantagens e desvantagens de usar o GraphQL

Vantagens:

  • Cliente define exatamente o que ele quer receber, portanto não há surpresas sobre as informações retornadas e todas as respostas são previsíveis. Como consequência disso, também há a redução de overfetching (recebimento de dados em excesso) e underfetching (não recebimento de todos os dados necessários).
  • Serve tudo em apenas um endpoint, ou seja, mesmo que sejam necessárias múltiplas chamadas, não é necessário registrar diferentes endpoints para isso, apenas informar qual operação que deve ser executada.
  • Permite utilizar vários recursos diferentes em apenas uma chamada, então muitas vezes exige menos chamadas do que seriam necessárias com uma API RESTful.
  • Permite adicionar ou depreciar campos sem afetar queries já existentes e consumidas, facilitando o trabalho de evolução dos dados retornados para o desenvolvedor.
  • Não exige uma arquitetura específica, ou seja, pode ser utilizada em conjunto com APIs RESTful e pode ser inserida em APIs já existentes sem necessidade de refatorar a arquitetura atual.
  • Facilidade de documentação, que é criada automaticamente pelo schema, portanto os processos de desenvolvimento e documentação são feitos em paralelo.

Desvantagens:

  • Mudança de paradigma. A maioria dos desenvolvedores já está muito acostumada com REST, então usar GraphQL pode exigir uma curva de aprendizado.
  • Sempre retorna status code 200, independentemente de ter sucesso ou não, portanto é necessário validar o body do JSON de retorno para erros.
  • Não possui suporte para caching automático, como o REST, o que pode aumentar a complexidade no desenvolvimento.
  • Nem sempre é fácil encontrar apoio na Internet, especialmente se o sistema não utilizar JS/React. A maioria dos exemplos encontrados são utilizando essas tecnologias, então pode ser difícil encontrar respostas diretas para problemas durante o desenvolvimento se estiver utilizando outras linguagens.
  • Dependendo dos dados, queries podem ter alto nível de complexidade. Como o cliente define exatamente o que quer receber, ele também precisa informar exatamente o que quer receber. Se a query possui uma estrutura de dados complexa, isso também aumenta o nível de dificuldade para o cliente realizar a requisição.

Quando usar GraphQL

  • Quando houver muitos dados aninhados e um grande volume de dados, e GraphQL pode ser útil para reduzir overfetching ou reduzir a quantidade de chamadas necessárias.
  • Estruturas de dados que podem ser alteradas com frequência, GraphQL reduz o impacto de alterações frequentes.
  • Aplicações mobile que precisam lidar com grandes volumes de dados, GraphQL pode reduzir o consumo de banda larga.
  • Quando for necessário obter dados de várias fontes diferentes, já que o GraphQL facilita o processo de obter tudo necessário em uma única chamada.

Estrutura de um projeto GraphQL

Mesmo que não exista uma arquitetura específica para projetos GraphQL, ainda temos uma estrutura que vai se repetir.

Essa estrutura começa com um schema, que será utilizado para apresentar toda a organização de dados da API, a relação entre os tipos e campos definidos pelo type system e todas as operações que podem ser realizadas dentro do schema.

Dentro desse schema, temos os types, as queries e as mutations. Types definem o type system, ou seja, definem a organização dos dados. Queries e mutations são as operações que o schema permite realizar. Queries são operações de obtenção de dados, e mutations são operações de alteração de dados. Analogamente aos verbos HTTP usados pelo REST, uma query funciona o equivalente ao GET, enquanto uma mutation pode funcionar como POST, PUT, PATCH ou DELETE. E, por fim, temos os resolvers. Cada operação da API está ligada a um resolver no backend, que é responsável por produzir o resultado do que foi requisitado pela chamada.

Agora, para todos esses conceitos ficarem um pouco mais claros, vamos criar uma aplicação usando o GraphQL.

Exemplo de Projeto: Loja de Departamentos

A situação: Uma loja precisa de um sistema para manipular os dados de seus produtos. É necessário conseguir obter um ou vários produtos, além de incluir e excluir produtos e alterar o preço de produtos já existentes.

Antes de seguirmos para o código, é bom já termos uma ideia básica de como será o schema desse projeto. Pela definição, precisamos de duas queries: uma que obtenha apenas um produto, e outra que obtenha uma lista de produtos. Também precisaremos de três mutations: uma para incluir produtos, uma para excluir, e uma para alterar preços. O type system irá depender do domínio.

Uma visualização do schema, contendo as queries, mutations e types que serão criados durante o desenvolvimento
Estrutura básica do schema

Projeto

O primeiro passo será criar um projeto Web Core App no Visual Studio:

Tela do Visual Studio de seleção do tipo de projeto, destacando o ASP .NET Core Web App

Nessa aplicação, usei o .Net 5, então há algumas diferenças sobre a configuração do GraphQL para o .Net 6. Para quem tiver interesse, podem ver como fazer a configuração para .Net 6 nesse projeto.

Estrutura

Tela do Solution Explorer do Visual Studio, apresentando os projetos que fazem parte da solução
Projetos da solução

Como podemos ver, essa solução vai consistir em 3 projetos. O Web App que criamos e será nosso Startup, um projeto para o nosso domínio e um para nossa infraestrutura, onde teremos nossa base de dados e a configuração do GraphQL.

O domínio será feito como em qualquer outro projeto .Net, então não vou entrar em muitos detalhes sobre ele, aqui o mais importante será a infraestrutura.

Dependências

Tela do Nuget Package Manager do Visual Studio, com as bibliotecas necessárias para a aplicação. Na ordem: GraphQL, GraphQL.Server.Core, GraphQL.Server.Transports.AspNetCore.SystemTextJson, GraphQL.Server.UI.Playground, Microsoft.AspNetCore.Hosting, e Microsoft.AspNetCore.Routing
Dependências necessárias para a aplicação, que devem ser instaladas no projeto GraphQL.Infrastructure

Base de dados

Como esse é apenas um exemplo simples de API, decidi criar uma base estática dentro do código:

Aqui, temos os produtos da nossa loja, com todas as informações estabelecidas no domínio: código, nome, descrição, preço, desconto à vista e departamento. Em um exemplo real, essa lista seria substituída pela própria base de dados do sistema, que vai funcionar da mesma forma independentemente de a API usar REST ou GraphQL.

Criando queries e mutations

A base para criar tanto queries quanto mutations em .Net é a mesma, e segue esse padrão:

A ideia de criação de qualquer operação vai ser criar uma classe que herda de ObjectGraphType (parte da biblioteca GraphQL que instalamos no projeto) e, dentro do construtor, inicializar todas as operações que vamos cadastrar nessa classe, seguindo o padrão desse exemplo genérico. Também usamos o construtor para injetar a(s) base(s) de dados que a classe precisa acessar.

Importante notar que essas classes do TipoRetorno e TipoParametro devem fazer parte do type system do GraphQL, não podemos usar tipos do C# nesses espaços.

Sabendo disso, vamos ver um exemplo de como criar uma query no nosso sistema de loja.

Como podemos ver, o padrão é bem parecido. Definimos o tipo de retorno do campo, seu nome, descrição, parâmetros de entrada e resolver. Essa é a estrutura base para qualquer operação do GraphQL. Mas tem um ponto interessante aqui. Na declaração do tipo de retorno e os tipos de parâmetros, nós colocamos tipos do GraphQL. No resolver, colocamos tipos do C#. Então como fazemos para garantir que os tipos das duas linguagens se entendam? Para isso, vamos entender melhor sobre o nosso type system.

Type System

Para o GraphQL funcionar, precisamos garantir que os tipos da nossa linguagem (no caso, C#) e do GraphQL possam ser convertidos de um para o outro, porque o GraphQL não entende tipos da linguagem.

Mas também não precisamos criar todos os tipos. A biblioteca GraphQL.Types já converte os tipos padrão do C#, como int, float e string (para detalhes sobre todos os tipos pré-mapeados, podem acessar essa página), o que já resolve grande parte dos nossos problemas.

E como resolver outros tipos, como Produto, Preco e Departamento, que criamos no nosso domínio? Para isso, vamos criar nosso type system, que será dividido em duas partes: tipos de input e tipos de domínio. Tipos de input servem para criar tipos que serão enviados como parâmetros de entrada em alguma operação do nosso schema, enquanto os tipos de domínio servem para mapear os tipos que serão retornados para o cliente. O principal motivo para a necessidade de separar esses dois grupos são os resolvers. Tipos input não precisam deles porque eles só recebem dados de entrada. Os tipos de domínio, no entanto, precisam ser capazes de converter o campo do tipo GraphQL para o tipo da linguagem, o que só é possível com os resolvers.

Vamos ver então como ficam os tipos ProdutoInput e ProdutoType no nosso sistema.

As principais diferenças entre esses dois tipos são:

Schema e configuração

Já está quase pronto! Agora só precisamos declarar nosso schema, que vai englobar todas as operações e tipos que criamos até o momento, e realizar o setup do GraphQL.

Declarar o schema é fácil, basta criar uma classe que herde de Types.Schema e declarar de onde o schema deve obter suas queries e mutations.:

Agora, só precisamos fazer as configurações.

Na função ConfigureGraphQL, instanciamos nossos serviços e adicionamos alguns métodos das bibliotecas que instalamos no começo do projeto para executar corretamente o GraphQL. Já na função UseGraphQL, estamos mapeando o endpoint do GraphQL e do Playground, a interface que vamos usar agora para testar nossa API.

Pronto! Agora podemos testar nossa API.

Realizando chamadas em GraphQL

Nesse artigo, vamos fazer nossas chamadas pelo Playground, uma interface do GraphQL para testar um schema.

Tela do Playground do GraphQL, com uma query de schema sendo executada
Playground do GraphQL

Como comentei mais cedo, o GraphQL facilita bastante o processo de documentação. Durante o desenvolvimento, nós colocamos descrições para nossos campos. Agora, o Playground já apresenta todo nosso schema, com as descrições que colocamos, nas duas abas à direita: docs e schema.

Tela da aba Docs do playground, apresentando detalhes sobre a query “produto”
Aba de documentação do Playground
Aba de schema do Playground, apresentando detalhes sobre as operações e tipos
Aba de detalhamento sobre o schema do Playground

Agora vamos executar uma query e uma mutation, para validar o funcionamento das nossas operações.

Imagem com um exemplo da query “produtos”, com o seguinte formato:{  produtos(departamento: ELETRONICOS) {  codigo  nome  preco {  valor  }  departamento  }  }
Exemplo de execução da query “produtos”

Aqui, estamos enviando uma requisição para a query “produtos”. Entre parênteses, passamos os parâmetros de entrada, no caso queremos obter os produtos do departamento de eletrônicos com um preço máximo de 5700. Depois, entre chaves, nós informamos o que queremos que seja retornado. Essa query retorna uma lista de produtos, então precisamos informar quais informações de produto que desejamos receber.

Retorno da queru “produtos”, retornando os campos requisitados no seguinte formato: {
 “data”: {
 “produtos”: [
 {
 “codigo”: “0001”,
 “nome”: “Tablet”,
 “preco”: {
 “valor”: 4200
 },
 “departamento”: “ELETRONICOS”
 },
 {
 “codigo”: “0002”,
 “nome”: “Notebook”,
 “preco”: {
 “valor”: 6000
 },
 “departamento”: “ELETRONICOS”
 },
 {
 “codigo”: “0003”,
Retorno da query executada

E esse é o retorno da requisição. Como podemos ver, dentro do objeto “data”, foi retornada uma lista de produtos com as exatas informações que pedimos. Isso mostra ambos os lados do GraphQL. De um lado, a resposta previsível, ou seja, já sabemos as informações que vamos receber no momento que fazemos a requisição. Pelo outro, nós exigimos que o cliente conheça a estrutura de produtos para ser capaz de definir as informações que quer receber.

Agora, vamos executar nossa mutation de alterar preços:

Imagem com um exemplo da mutation “alterarPreco”, com o seguinte formato: mutation {
 alterarPreco(codigo: “0006”, preco: { descontoAVista: 0.20 }) {
 codigo
 nome
 preco {
 valor
 descontoAVista
 }
 }
 }
Exemplo de execução da mutation “alterarPreco”

Podemos ver que a lógica é bem parecida. Nós informamos na entrada que o produto com código 0006 deve ter um desconto à vista de 0.20, em seguida informamos o que queremos receber no retorno. A única diferença é que, enquanto para queries podemos apenas abrir chaves na linha 1, agora precisamos explicitamente declarar que estamos fazendo uma chamada para uma mutation.

Retorno da mutation “alterarPreco” executada, retornando os dados no seguinte formato: {
 “data”: {
 “alterarPreco”: {
 “codigo”: “0006”,
 “nome”: “Caneta”,
 “preco”: {
 “valor”: 4.5,
 “descontoAVista”: 0.2
 }
 }
 },
 “extensions”: {}
 }
Retorno da mutation executada

E, novamente, nosso retorno é exatamente o que pedimos!

Obrigado pela leitura! Espero que tenha ficado claro como o GraphQL pode ser uma alternativa ao REST e que todos tenham compreendido como usar essa tecnologia com o .NET. Para mais detalhes, esse repositório do GitHub contém o código da aplicação usada no artigo e exemplos de requisições que podem ser feitas para cada uma das operações criadas, para todos que queiram brincar um pouco com o projeto ou entender alguns detalhes que não explorei aqui no artigo!

--

--