Fala Galera,
GraphQL é uma tecnologia que basicamente foi desenvolvida pelo Facebook para o consumo de sua API. Ele é uma alternativa ao OData (Open Data Protocol) e sua principal missão é fazer descoberta e pesquisa de dados de forma simples e robusta.
GraphQL consiste em duas partes; Um sistema de tipo no qual descreve o esquema de dados que está disponivel em nosso servidor e uma linguagem de consulta que permite a busca dos dados que necessitamos.
E quais são seus principios de arquitetura ?
- Objetos Hierarquicos: O GraphQL trabalha para criação e manipulacão de hierarquias de objetos. Uma consulta GraphQL é em si um conjunto hierarquico de campos. A consulta é moldada na maneira de como você quer que os dados retornem.
- Centrado no Produto: O GraphQL é pensado sempre produto e suas necessidades não precisamos nos preocupar em quais campos e como os campos são retornados pois a consulta é montada conforme a especificação da view e não precisamos mexer em nada no nosso backend para retornar mais campos ou menos campos no mesmo endpoint.
- Client Queries: Em GraphQL as consultas são montadas no consumidor da nossa API em vez de ser montada no servidor. Na grande maioria dos aplicativos escritos sem GraphQL, o servidor determina os dados retornados em seus endpoints. Por outro lado o GraphQL retorna exatamente o que o cliente pede e sem precisarmos mexer em nosso backend.
- Strongly-typed: GraphQL é fortemente tipado. Dada uma consulta, o sistema de tipo do GraphQL verifica os tipos e a sintaxe das Querys, validandos as informações e garantido a qualidade dos dados.
Veja um exemplo de uma consulta construida em GraphQL:
{ user(id: 3500401) { id, name, isViewerFriend, profilePicture(size: 50) { uri, width, height } } }
E a resposta do nosso servidor será essa aqui:
{ "user" : { "id": 3500401, "name": "Jing Chen", "isViewerFriend": true, "profilePicture": { "uri": "http://someurl.cdn/pic.jpg", "width": 50, "height": 50 } } }
Construindo uma API em GraphQL:
Para demonstrar o uso do GraphQL, vamos criar um servidor de consulta em GraphQL utilizando .NET. O GraphQL está disponivel em diversas linguagem e claro o .NET não poderia ficar para trás.
Para o nosso exemplo, crie um console application e após a criação do projeto vamos adicionar um pacote chamado GraphQL, esse pacote é a implementação basica do GraphQL em .NET. Para adicionar o pacote digite o comando abaixo no Package Manager Console.
- Install-Package GraphQL
- Microsoft.AspNet.WebApi.SelfHost
Nossa aplicação será um WebApi Self Host com isso vamos disponibilizar um endpoint de consulta em GraphQL Rest.
Após a instalação dos pacotes vamos configurar o Self Host abra o arquivo program.cs e adicione o código abaixo:
static void Main(string[] args) { Uri _baseAddress = new Uri("http://localhost:60064/"); HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(_baseAddress); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); var bootstrapper = new Bootstrapper(); config.DependencyResolver = bootstrapper.Resolver(); // Create server var server = new HttpSelfHostServer(config); // Start listening server.OpenAsync().Wait(); Console.WriteLine("Web API Self hosted on " + _baseAddress + " Hit ENTER to exit..."); Console.ReadLine(); server.CloseAsync().Wait(); }
Com o Self Host configurado vamos adicionar a injeção de dependência, o GraphQL necessita dessse tipo de configuração para funcionar.
Configurando a injeção de Dependência do GraphQL
Adicione um novo arquivo chamado Bootstrapper.cs. Ele será o responsável por registrar nossas dependências.
public class Bootstrapper { public IDependencyResolver Resolver() { var container = BuildContainer(); var resolver = new SimpleContainerDependencyResolver(container); return resolver; } private ISimpleContainer BuildContainer() { var container = new SimpleContainer(); container.Singleton<IDocumentExecuter>(new DocumentExecuter()); container.Singleton<IDocumentWriter>(new DocumentWriter(true)); container.Singleton(new StarWarsData()); container.Register<StarWarsQuery>(); container.Register<HumanType>(); container.Register<DroidType>(); container.Register<CharacterInterface>(); container.Singleton(new StarWarsSchema(type => (GraphType)container.Get(type))); return container; } }
Veja que temos classes que ainda não foram criadas, elas serão criadas ao longo desse post. Agora temos que criar nosso container de resolução de serviço. Crie um arquivo chamado SimpleContainer.cs e adicione o código abaixo:
public interface ISimpleContainer : IDisposable { object Get(Type serviceType); T Get<T>(); void Register<TService>(); void Register<TService>(Func<TService> instanceCreator); void Register<TService, TImpl>() where TImpl : TService; void Singleton<TService>(TService instance); void Singleton<TService>(Func<TService> instanceCreator); } public class SimpleContainer : ISimpleContainer { private readonly Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>(); public void Register<TService>() { Register<TService, TService>(); } public void Register<TService, TImpl>() where TImpl : TService { _registrations.Add(typeof(TService), () => { var implType = typeof(TImpl); return typeof(TService) == implType ? CreateInstance(implType) : Get(implType); }); } public void Register<TService>(Func<TService> instanceCreator) { _registrations.Add(typeof(TService), () => instanceCreator()); } public void Singleton<TService>(TService instance) { _registrations.Add(typeof(TService), () => instance); } public void Singleton<TService>(Func<TService> instanceCreator) { var lazy = new Lazy<TService>(instanceCreator); Register(() => lazy.Value); } public T Get<T>() { return (T)Get(typeof(T)); } public object Get(Type serviceType) { Func<object> creator; if (_registrations.TryGetValue(serviceType, out creator)) { return creator(); } if (!serviceType.IsAbstract) { return CreateInstance(serviceType); } throw new InvalidOperationException("No registration for " + serviceType); } public void Dispose() { _registrations.Clear(); } private object CreateInstance(Type implementationType) { var ctor = implementationType.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First(); var parameterTypes = ctor.GetParameters().Select(p => p.ParameterType); var dependencies = parameterTypes.Select(Get).ToArray(); return Activator.CreateInstance(implementationType, dependencies); } }
Esse é o arquivo responsável pela resolução dos nomes e pela a criação das instâncias dos objetos. Para finalizar precisamos agora criar o Dependency Resolver. Adicione um novo arquivo chamado SimpleContainerDependencyResolver.cs e adicione o código abaixo.
public class SimpleContainerDependencyResolver : IDependencyResolver { private readonly ISimpleContainer _container; public SimpleContainerDependencyResolver(ISimpleContainer container) { _container = container; } public object GetService(Type serviceType) { try { return _container.Get(serviceType); } catch (Exception) { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } public IDependencyScope BeginScope() { return this; } public void Dispose() { } }
Nossa injeção de dependência está configurada, agora temos a missão de configurar nosso schema GraphQL este é o ultimo passo para criar API Rest GraphQL
Criando um Schema GraphQL
Para que nossa API GraphQL funcione precisamos configurar o nosso schema de consulta e seu objetos para esse post eu utilizei um exemplo de schema do StarWars o mesmo usado pela biblioteca GraphQL.NET no seu exemplo.
Primeira coisa que devemos fazer é o Objeto de consulta, ele será o responsável por resolver nossas query’s. Vamos adicionar um novo arquivo chamado StarWarsQuery.cs com o código abaixo:
public class StarWarsQuery : ObjectGraphType<object> { public StarWarsQuery(StarWarsData data) { Name = "Query"; Field<CharacterInterface>("hero", resolve: context => data.GetDroidByIdAsync("3")); Field<HumanType>( "human", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "id", Description = "id of the human" } ), resolve: context => data.GetHumanByIdAsync(context.GetArgument<string>("id")) ); Field<DroidType>( "droid", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "id", Description = "id of the droid" } ), resolve: context => data.GetDroidByIdAsync(context.GetArgument<string>("id")) ); } }
Agora que o StarWarsQuery está pronto, precisamos adicionar nossos objetos que serão usados pela consulta. Para isso adicione um novo arquivo chamado StarWarsCharacter, ele será o responsável pelo nosso modelo de dados.
public abstract class StarWarsCharacter { public string Id { get; set; } public string Name { get; set; } public string[] Friends { get; set; } public int[] AppearsIn { get; set; } } public class Human : StarWarsCharacter { public string HomePlanet { get; set; } } public class Droid : StarWarsCharacter { public string PrimaryFunction { get; set; } } public class EpisodeEnum : EnumerationGraphType { public EpisodeEnum() { Name = "Episode"; Description = "One of the films in the Star Wars Trilogy."; AddValue("NEWHOPE", "Released in 1977.", 4); AddValue("EMPIRE", "Released in 1980.", 5); AddValue("JEDI", "Released in 1983.", 6); } } public enum Episodes { NEWHOPE = 4, EMPIRE = 5, JEDI = 6 } #endregion public class CharacterInterface : InterfaceGraphType<StarWarsCharacter> { public CharacterInterface() { Name = "Character"; Field(d => d.Id).Description("Id do Personagem"); Field(d => d.Name, nullable: true).Description("Nome do Personagem"); Field<ListGraphType<CharacterInterface>>("friends"); Field<ListGraphType<EpisodeEnum>>("appearsIn", "Qual filme apareceu"); } }
Nosso modelo de dados está pronto, vamos criar um novo arquivo chamado StarWarsData, ele será responsável por fazer a consulta no banco de dados, resumindo em uma aplicação real seria ele que iria consultar nosso banco de dados.
public class StarWarsData { #region Types public class HumanType : ObjectGraphType<Human> { public HumanType(StarWarsData data) { Name = "Human"; Field(h => h.Id).Description("The id of the human."); Field(h => h.Name, nullable: true).Description("The name of the human."); Field<ListGraphType<CharacterInterface>>( "friends", resolve: context => data.GetFriends(context.Source) ); Field<ListGraphType<EpisodeEnum>>("appearsIn", "Which movie they appear in."); Field(h => h.HomePlanet, nullable: true).Description("The home planet of the human."); Interface<CharacterInterface>(); } } public class DroidType : ObjectGraphType<Droid> { public DroidType(StarWarsData data) { Name = "Droid"; Description = "A mechanical creature in the Star Wars universe."; Field(d => d.Id).Description("The id of the droid."); Field(d => d.Name, nullable: true).Description("The name of the droid."); Field<ListGraphType<CharacterInterface>>( "friends", resolve: context => data.GetFriends(context.Source) ); Field<ListGraphType<EpisodeEnum>>("appearsIn", "Which movie they appear in."); Field(d => d.PrimaryFunction, nullable: true).Description("The primary function of the droid."); Interface<CharacterInterface>(); } } #endregion private readonly List<Human> _humans = new List<Human>(); private readonly List<Droid> _droids = new List<Droid>(); public StarWarsData() { _humans.Add(new Human { Id = "1", Name = "Luke", Friends = new[] { "3", "4" }, AppearsIn = new[] { 4, 5, 6 }, HomePlanet = "Tatooine" }); _humans.Add(new Human { Id = "2", Name = "Vader", AppearsIn = new[] { 4, 5, 6 }, HomePlanet = "Tatooine" }); _droids.Add(new Droid { Id = "3", Name = "R2-D2", Friends = new[] { "1", "4" }, AppearsIn = new[] { 4, 5, 6 }, PrimaryFunction = "Astromech" }); _droids.Add(new Droid { Id = "4", Name = "C-3PO", AppearsIn = new[] { 4, 5, 6 }, PrimaryFunction = "Protocol" }); } public IEnumerable<StarWarsCharacter> GetFriends(StarWarsCharacter character) { if (character == null) { return null; } var friends = new List<StarWarsCharacter>(); var lookup = character.Friends; if (lookup != null) { friends.AddRange(_humans.Where(h => lookup.Contains(h.Id))); friends.AddRange(_droids.Where(d => lookup.Contains(d.Id))); } return friends; } public Task<Human> GetHumanByIdAsync(string id) { return Task.FromResult(_humans.FirstOrDefault(h => h.Id == id)); } public Task<Droid> GetDroidByIdAsync(string id) { return Task.FromResult(_droids.FirstOrDefault(h => h.Id == id)); } }
Por último e não menos importante é o StarWarsSchema, ele é o responsável pelo gerenciamento do nosso schema. Adicione um novo arquivo chamado StarWarsSchema.cs e coloque o código abaixo:
public class StarWarsSchema : Schema { public StarWarsSchema(Func<Type, GraphType> resolveType) : base(resolveType) { Query = (StarWarsQuery)resolveType(typeof(StarWarsQuery)); } }
Agora nosso Schema GraphQL e nosso modelo de dados estão pronto. Vamos expor uma API Rest para que possamos consultar os dados.
Criando uma WebApi GraphQL
Para expor nosso modelo de dados precisamos criar um WebApi. Vamos criar um novo arquivo chamado GraphQLController e adicionar o código abaixo:
public class GraphQLController : ApiController { private readonly ISchema _schema; private readonly IDocumentExecuter _executer; private readonly IDocumentWriter _writer; public GraphQLController( IDocumentExecuter executer, IDocumentWriter writer, StarWarsSchema schema) { _executer = executer; _writer = writer; _schema = schema; } [HttpGet] public Task<HttpResponseMessage> GetAsync(HttpRequestMessage request) { try { return PostAsync(request, new GraphQLQuery { Query = "query { hero { id, name } }", Variables = "" }); } catch (Exception ex) { return null; } } [HttpPost] public async Task<HttpResponseMessage> PostAsync(HttpRequestMessage request, GraphQLQuery query) { var inputs = query.Variables.ToInputs(); var queryToExecute = query.Query; var result = await this._executer.ExecuteAsync(_ => { _.Schema = _schema; _.Query = queryToExecute; _.OperationName = query.OperationName; _.Inputs = inputs; _.ComplexityConfiguration = new ComplexityConfiguration { MaxDepth = 15 }; _.FieldMiddleware.Use<InstrumentFieldsMiddleware>(); }).ConfigureAwait(false); var httpResult = result.Errors?.Count > 0 ? HttpStatusCode.BadRequest : HttpStatusCode.OK; var json = this._writer.Write(result); var response = request.CreateResponse(httpResult); response.Content = new StringContent(json, Encoding.UTF8, "application/json"); return response; } } public class GraphQLQuery { public string OperationName { get; set; } public string NamedQuery { get; set; } public string Query { get; set; } public string Variables { get; set; } }
Agora está tudo pronto para nossa API Graph funcionar. Vamos testar.
Fazendo consulta na API GraphQL
Para testar nossa API vamos utilizar o Postman, ele é uma extensão do Chrome no qual permite relizar requisições em API Rest. Para testar nossa aplicação, vamos executar nosso projeto no Visual Studio apertando F5. Quando tiver rodando, abra a extensão Postman e realize uma consulta conforme mostrada na imagem abaixo se tudo estiver correto você deverá ver uma resposta igual a minha.
PS: Execute o Visual Studio em modo Administrador
O GraphQL é uma otima alternativa para se criar API Rest. Ele com pouca linha de códigos conseguimos ter uma API funcional, extensiva e robusta e o melhor trazemos somente os campos que foram solicitados dimuindo assim o tráfego de rede e dados trafego na rede.
Para mais informações sobre o GraphQL veja a sua documentação clicando aqui
O código deste exemplo está em meu GitHub atráves deste link
Abs e até a próxima
Muito bom o post! Resume, explica e exemplifica muito bem a tecnologia
Muito obrigado pela visita.
Abs,
Muito bom o seu tutorial.
Eu baixei o seu projeto no github e consegui executar tranquilo (como administrador ), só que aparece a seguinte mensagem de erro quando eu tento executar um post:
{
“Message”: “No HTTP resource was found that matches the request URI ‘http://localhost:60064/api/graphQL’.”,
“MessageDetail”: “No type was found that matches the controller named ‘graphQL’.”
}
Você tem alguma ideia de como arrumar isso?
Ola Bruno,
Verificou se o projeto esta rodando no mesmo endereço/porta ?
Abs.,
Comigo aconteceu a mesma coisa
O projeto do git não está com a GraphQLController
Olá Bruno,
Desculpa o engano, vou atualizar o projeto git com o arquivo faltante.
Abs