GraphQL + NET
O que é GraphQL?
GraphQL é uma linguagem de consulta (query language) para APIs. Ele é também um runtime para atender consultas de dados. O serviço GraphQL é independente de transporte, mas em geral é servido sobre HTTP.
O GraphQL foi desenvolvido em 2012 pelo Facebook. A história conta que o Facebook utilizava RESTful e estava enfrentando problemas de desempenho, e com frequência apresentavam falhas. Foi então que a equipe de engenheiros perceberam que precisavam melhorar o modo como os dados estavam sendo enviados.
Mas afinal qual problema o GraphQL se propõe a resolver?
Overfetching
Com o GraphQL você não tem o problema de overfeching, que é receber mais dados do que precisa, (uma busca de dados em excesso).
Ao consumir um endpoint em REST, independente do que irá precisar, seja apenas o nome e o id do cliente, por ex. você sempre irá receber todos os dados que aquele endpoint disponibiliza. Para resolver isso, você teria que criar um série de endpoints para cada necessidade especifica, o que pode gerar outros problemas.
Com GraphQL, você tem apenas um endpoint, e pode solicitar apenas os dados que precisa naquele momento.
Desvantagem
Uma desvantagem que vejo no uso do GraphQL é que ele sempre responde status 200, seja uma query ou uma mutation. Então você perde o poder da validação do status code.
E a beleza da invocação de endpoints por verbos, como no RESTful, se perde, por que no GraphQL tudo é POST.
Implementando uma API GraphQL com .NET
Primeiro passo será definir a estrutura básica de um projeto, então vamos começar pela entidade. Será um exemplo simples, o foco será a implementação do GraphQL.
Entidade
public class Cliente
{
[Key]
public int Id { get; private set; }
public string Nome { get; private set; }
public string Sobrenome { get; private set; }
public DateTime DataNascimento { get; private set; }
public bool Ativo { get; private set; }
public Cliente(string nome, string sobrenome, DateTime dataNascimento, bool ativo)
{
Nome = nome;
Sobrenome = sobrenome;
DataNascimento = dataNascimento;
Ativo = ativo;
}
}
Context
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public AppDbContext() { }
public DbSet<Cliente> Clientes { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var connectionString = configuration.
GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
}
}
Repository Interface
public interface IClienteRepository
{
IEnumerable<Cliente> GetAll();
Cliente Get(int id);
void Add(Cliente cliente);
}
Repository
public class ClienteRepository : IClienteRepository
{
private readonly AppDbContext _context;
public ClienteRepository(AppDbContext context)
{
_context = context;
}
public void Add(Cliente cliente)
{
using (_context)
{
_context.Clientes.Add(cliente);
_context.SaveChanges();
}
}
public Cliente Get(int id) => _context.Clientes.FirstOrDefault(x => x.Id == id);
public IEnumerable<Cliente> GetAll() => _context.Clientes.ToList();
}
Com essas peças básicas prontas, vamos definir os tipos do GraphQL. Lembre-se que a estrutura acima está simples de forma intencional, em uma modelagem real, as coisas são um “pouco” diferentes.
Antes de seguir instale os pacotes do GraphQL.Net
Para facilitar segue…
<PackageReference Include="graphiql" Version="2.0.0" />
<PackageReference Include="GraphQL" Version="7.3.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.3.0" />
<PackageReference Include="GraphQL.Server.Ui.Playground" Version="7.3.0" />
Feito isso oprimeiro passo é definir o ClienteType que será o nosso tipo GraphQL para representar os dados.
public class ClienteType : ObjectGraphType<Cliente>
{
public ClienteType()
{
Name = "clientes";
Field(x => x.Id, type: typeof(IdGraphType))
.Description("Propriedade Id do objeto cliente.");
Field(x => x.Nome, type: typeof(StringGraphType))
.Description("Propriedade Nome do objeto cliente.");
Field(x => x.Sobrenome, type: typeof(StringGraphType))
.Description("Propriedade Sobrenome do objeto cliente.");
Field(x => x.DataNascimento, type: typeof(DateGraphType))
.Description("Propriedade DataNascimento do objeto cliente.");
Field(x => x.Ativo, type: typeof(BooleanGraphType))
.Description("Propriedade Ativo do objeto cliente.");
}
}
Vamos agora criar nossa classe para permitir a execução de queries.
public class ClienteQuery : ObjectGraphType<object>
{
public ClienteQuery(IClienteRepository repository)
{
Name = "ClienteQuery";
Field<ListGraphType<ClienteType>>(
"clientes",
resolve: context => repository.GetAll());
Field<ClienteType>(
"cliente",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context => {
return repository.Get(context.GetArgument<int>("id"));
});
}
}
Abaixo segue a implementação da mutation para a realização de comandos de alteração.
public class ClienteMutation : ObjectGraphType
{
public ClienteMutation(IClienteRepository repository)
{
Name = "ClienteMutation";
Field<ClienteType>(
"cadastrarcliente",
arguments: new QueryArguments(
new QueryArgument<StringGraphType> { Name = "nome" },
new QueryArgument<StringGraphType> { Name = "sobrenome" },
new QueryArgument<DateGraphType> { Name = "datanascimento" },
new QueryArgument<BooleanGraphType> { Name = "ativo" }
),
resolve: context =>
{
var cliente = new Cliente(context.GetArgument<string>("nome"),
context.GetArgument<string>("sobrenome"),
context.GetArgument<DateTime>("datanascimento"),
context.GetArgument<bool>("ativo")
);
repository.Add(cliente);
return cliente;
});
}
}
Por fim definimos o Schema.
public class AppScheme : Schema
{
public AppScheme(IDependencyResolver resolver) : base(resolver)
{
Query = resolver.Resolve<ClienteQuery>();
Mutation = resolver.Resolve<ClienteMutation>();
}
}
Agora vamos configurar nossa classe Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>();
services.AddScoped<IClienteRepository, ClienteRepository>();
services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
services.AddScoped<AppScheme>();
services.AddGraphQL(o => { o.ExposeExceptions = false; }).AddGraphTypes(ServiceLifetime.Scoped);
services.Configure<IISServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseGraphiQl();
app.UseGraphQL<AppScheme>();
}
Você pode adicionar no launchSettings.json
"GraphQL": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "graphql",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
Basta executar o projeto e testar com o playground.
Mutation
Para realizar alguns testes, execute a mutation abaixo. O cadastro será realizado e irá retornar o id gerado.
mutation{
cadastrarcliente(nome:"James", sobrenome:"Hetfield", ativo:true, datanascimento:"1963/08/03")
{
id
}
}
Query
Execute as queries abaixo para ver os resultado. Selecionando todos os clientes.
{
clientes {
id,
nome,
sobrenome
}
}
Ou selecionando um cliente especifico.
{
cliente(id: 11) {
id
nome
sobrenome
}
}
Para mais informações sobre a construção de queries, mutation, types, schema e mais acesse https://graphql.org/
Muito obrigado! Até a próxima.