GraphQL - ASP.NET Core com HotChocolate - I
 Neste post veremos como implementar o GraphQL em uma aplicação ASP .NET Core usando o HotChocolate.


O GraphQL é uma linguagem de consulta para sua API e um runtime do lado do servidor para executar consultas usando um sistema de tipos que você define para seus dados. Ele permite que você especifique exatamente quais dados você precisa.


 



"O GraphQL não está vinculado a nenhum banco de dados ou mecanismo de armazenamento específico e, em vez disso, é respaldado pelo código e pelos dados existentes."

Dessa forma o GraphQL facilita o processo de entregar ao cliente apenas o que foi requisitado pelo mesmo e na ordem em que foi solicitado.

Ele pode ser entendindo como uma abstração ao protocolo HTTP onde você usa um endpoint que usa um server GraphQL que receberá requisições do tipo POST e GET respondendo no formato JSON.

Alguns conceitos usados no ecosistema GraphQL:

Schema - Descreve a funcionalidade disponível para os clientes que se conectam a ele.

Tipos - Tudo em GraphQL é um tipo,e, um tipo pode ser entendido como uma entidade em seu banco de dados (relacional ou não relacional). Os tipos forma um schema, e, o GraphQL possui 2 tipos padrão: RootQuery e RootMutation.

Query e Mutations - O type Query serve para definir o contrato de consulta de dados e O type Mutation serve para definir o contrato de manipulação de dados

Resolver - Resolvers são funções responsáveis revolver um pedido e devolver o dado solicitado. As funções recebem três argumentos : root , argumentos e contexto.

É importante ressaltar que embora seja uma linguagem de consulta para APIs o GraphQL é diferente do REST, na verdade ele compete como o REST.

HotChocolate e GraphQL

Neste projeto iremos usar o HotChocolate que é uma .NET GraphQL que nos ajuda a construir uma camada GraphQL sobre a nossa aplicação; O HotChocolate é muito fácil de configurar e elimina a confusão de escrever esquemas GraphQL.

Ele permite que criar e hospedar esquemas e, em seguida, atender consultas usando os mesmos componentes básicos do GraphQL — carregador de dados, resolver, schema, tipos e operações.

O Hot Chocolate vem com um conjunto de middleware ASP.NET Core usado para disponibilizar o servidor GraphQL via HTTP e WebSockets. Também há middleware para hospedar o GraphQL IDE Banana Cake Pop, bem como um endpoint usado para baixar o esquema em sua representação SDL.

Ao utilizar o GrapQL, minimizamos o número de viagens de ida e volta feitas do cliente ao servidor para obter todos os dados necessários. Quando usamos REST, temos que visitar um endpoint específico para obter os dados de que precisamos, pois é criada uma ação dentro de um controlador para servir apenas esses dados.

Uma coisa que pode ser confusa sobre grandes aplicativos usando REST é o número de endpoints (pode ser enorme), usando o GraphQL podemos eliminar isso, pois temos apenas um endpoint. Outra grande coisa sobre o GrapQL é que você só recebe o que pede.

Você não obterá muitos dados de que não precisa e o motivo é que uma consulta GrapQL é definida por campos que você gostaria de retornar, simples assim.

Assim podemos dizer que algumas vantagens do GraphQL são:

Neste artigo iremos criar uma aplicação ASP .NET Core Web API e usar bibliotecas Nuget para implementar a utilização do GraphQL em nossa API.

Vamos definir uma API para gerenciar informações de Funcionarios e Departamentos fazendo um CRUD Básico.

recursos usados:

Criando a solução e o projeto

 

No VS 2022 crie um novo projeto do tipo ASP .NET Core Web Api com o nome GraphQLNet :



Defina as opções para criar o projeto conforme mostra a figura a seguir:
 

 

Não iremos precisar da interface do Swagger nem vamos usar Controllers em nosso projeto.

 

Vamos instalar as seguintes dependências no projeto:

Isso pode ser feito via menu Tools->..->Manage Nuget Packages for Solution usando a guia Browser ou via linha de comando : install-package <nome_pacote>

 

Criando o modelo de dominio e o contexto

Crie no projeto a pasta Models e nesta pasta crie as classes Funcionario e Departamento que representam o modelo de domínio da aplicação:

 

1- Departamento
 

public class Departamento
{
    public int DepartamentoId { get; set; }
    [Required]
    public string? Nome { get; set; }
    public ICollection<Funcionario>? Funcionarios { get; set; }
}

 

2- Funcionario

 

public class Funcionario
{
    public int FuncionarioId { get; set; }
    [Required]
    public string? Nome { get; set; }
    [Required]
    [EmailAddress]
    public string? Email { get; set; }
    [Required]
    [Range(minimum: 20, maximum: 60)]
    public int Idade { get; set; }
    public int DepartamentoId { get; set; }
    public Departamento? Departamento { get; set; }
}


Crie a pasta DataContext no projeto e nesta pasta crie a classe AppDbContext que herda de DbContext e representa o contexto do EF Core onde definimos o mapeamento ORM:

 

1- AppDbContext
 

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    { }
    public DbSet<Funcionario>? Funcionarios { get; set; }
    public DbSet<Departamento>? Departamentos { get; set; }
}

 

 

Criando os Repositórios

Vamos implementar o padrão Repository para criar uma camada de acesso aos dados usando o contexto do EF Core.

 

Crie uma pasta Repositories no projeto e a seguir crie as interfaces IDepartamentoRepository e IFuncionarioRepository e as classes concretas FuncionarioRepository e DepartamentoRepository que implementam estas interfaces.

 

1- IDepartamentoRepository

 

public interface IDepartamentoRepository
{
    List<Departamento> GetListaDepartamentos();
    List<Departamento> GetDepartamentosComFuncionarios();
    Task<Departamento> CriarDepartamento(Departamento departamento);
}

 

2- DepartamentoRepository

 

public class DepartamentoRepository: IDepartamentoRepository
{
    private readonly AppDbContext _context;
    public DepartamentoRepository(AppDbContext context)
    {
        _context = context;
    }
    public List<Departamento> GetListaDepartamentos()
    {
        var departamentos = _context.Departamentos.ToList();
        return departamentos;
    }
    public List<Departamento> GetDepartamentosComFuncionarios()
    {
        return _context.Departamentos
               .Include(d => d.Funcionarios)
               .ToList();
    }
    public async Task<Departamento> CriarDepartamento(Departamento departamento)
    {
        await _context.Departamentos.AddAsync(departamento);
        await _context.SaveChangesAsync();
        return departamento;
    }
}

 

3- IFuncionarioRepository

 

public interface IFuncionarioRepository
{
    List<Funcionario> GetFuncionarios();
    Funcionario GetFuncionarioPorId(int id);
    List<Funcionario> GetFuncionariosComDepartamento();
    Task<Funcionario> CriarFuncionario(Funcionario funcionario);
}

 

4- FuncionarioRepository

public class FuncionarioRepository : IFuncionarioRepository
{
    private readonly AppDbContext _context;
    public FuncionarioRepository(AppDbContext context)
    {
        _context = context;
    }
    public List<Funcionario> GetFuncionarios()
    {
        return _context.Funcionarios.ToList();
    }
    public Funcionario GetFuncionarioPorId(int id)
    {
        var employee = _context.Funcionarios
            .Include(e => e.Departamento)
            .Where(e => e.FuncionarioId == id)
            .FirstOrDefault();
        if (employee != null)
            return employee;
        return null;
    }
    public List<Funcionario> GetFuncionariosComDepartamento()
    {
        return _context.Funcionarios
            .Include(e => e.Departamento)
            .ToList();
    }
    public async Task<Funcionario> CriarFuncionario(Funcionario funcionario)
    {
        await _context.Funcionarios.AddAsync(funcionario);
        await _context.SaveChangesAsync();
        return funcionario;
    }
}

 

Registrando os serviços e aplicando o Migrations

Agora vamos registrar os serviços e o contexto no arquivo Program do projeto :

using GraphQLNet.DataAccessGraphQL;
using GraphQLNet.DataContext;
using GraphQLNet.Repositories;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext<AppDbContext>(options =>
              options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IFuncionarioRepository, FuncionarioRepository>();
builder.Services.AddScoped<IDepartamentoRepository, DepartamentoRepository>();
string AllowedOrigin = "allowedOrigin";
builder.Services.AddCors(option =>
{
    option.AddPolicy("AllowedOrigin",
        builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
        );
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseCors(AllowedOrigin);
app.UseWebSockets();
app.UseRouting();

app.Run();

 

Neste código registramos o serviço do contexto AppDbContext e dos Repositórios e também incluímos uma política CORS.

 

A seguir defina a string de conexão no arquivo appsettings.json:

 

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=SQLEXPRESS;Initial Catalog=GraphQLDB;Integrated Security=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}


Agora podemos aplicar o Migrations para criar o banco de dados RabitMQDB e a tabela Products no SQL Server :

 

dotnet ef migrations add Inicial

dotnet ef database update

 

Ao final podemos consulta a janela Server Explorer do VS 2022 e verificar o banco e as tabelas criadas:
 

 

Vamos incluir alguns dados nas tabelas para realizar os testes como mostrado a seguir:

 

1- Departamentos
 

 

2- Funcionarios
 

 

Na próxima parte do artigo vamos configurar o serviço do GraphQL e criar a query, mutation e subscription.

 

"Não sabeis vós que os que correm no estádio, todos, na verdade, correm, mas um só leva o prêmio? Correi de tal maneira que o alcanceis. E todo aquele que luta de tudo se abstém; eles o fazem para alcançar uma coroa corruptível; nós, porém, uma incorruptível."
1 Coríntios 9:24,25

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti