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:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design
HotChocolate.AspNetCore
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
Referências: