f Skip to main content

Recientemente empecé con algunos proyectos, que por requerimiento de cliente, incorporaban GraphQL en la solución. Mi experiencia con este lenguaje de consultas neutro y agnóstico de plataforma era nula y el reto es hacer fácil el uso del mismo en proyectos con NETCore (preferiblemente incorporar este lenguaje en nuestras plantillas llamadas bloques).

GraphQL nos provee una interfaz declarativa mediante la cual podemos obtener datos de un servidor, podemos reducir el número de viajes ida y vuelta al backend para obtener datos que vía REST implicaría varias llamadas y también podemos obtener solo información relevante (solo los campos atributos que necesitamos, lo cual reduciría el tamaño de los datos a transferir y por ende una respuesta más rápida).

Si te preguntas como GraphQL reduce el número de llamadas al backend, te explico: en GraphQL, se puede cruzar desde el endpoint de entrada a los datos afines o requeridos, siguiendo las relaciones definidas en el esquema en una sola solicitud. En REST, tienes tantos endpoinds como recursos y debes llamar cada uno de los recursos relacionados.

Hablemos de los elementos fundamentales de GraphQL

Bloques de construcción

Entre los bloques de construcción importantes de GraphQL encontramos los siguientes:

Schema

El mecanismo empleado para hacerle saber al consumidor que acciones se pueden desencadenar y que datos produce se llama Schema. En nuestro caso es una clase que extiende de la clase Schema en el namespace GraphQL.Types.

Un esquema está compuesto de:

  1. Query: se usan para ir a buscar o leer datos.
  2. Mutation: estas son usadas para escribir o enviar datos al servicio.
  3. Suscription: son una característica que permite al servidor enviar datos a los clientes cuando un evento específico sucede. Estas se apoyan en Websockets y en esta configuración el servidor mantiene una conexión estable con sus clientes suscritos.

GraphQL Object Types

La finalidad de los object types es representar el tipo de objeto a recuperar de la api, estos están representados por la clase GraphQL.Types.ObjectGraphType

Al código. ¡De una!

En esencia vamos a crear un proyecto NETCore, en mi caso voy con netcore 3.1 – pero al momento de hacer este documento ya .NET5 está disponible – y vamos a configurar el middleware de GraphQL

Cree su proyecto con dotnet (puede ser una consola de PowerShell, CMD o cualquier shell si es Linux)

 dotnet new webapi -n project-name 

Instale estos paquetes (dotnet add package o Install-Package)

dotnet add package GraphQL
dotnet add package GraphiQL
dotnet add package GraphQL.Server.Transports.AspNetCore
dotnet add package GraphQL.Server.Transports.AspNetCore.SystemTextJson
dotnet add package GraphQL.SystemTextJson 

Para verificar que nuestro middleware está funcionando, añada la siguiente instrucción en el método Configure de la clase StartUp (yo estoy usando vscode, pero podrías usar cualquier ide)

  app.UseGraphiQL("/graphql"); 

Ejecute el proyecto y vayamos a la url http://localhost:5000/graphql, debe obtener la siguiente salida y con esto comprobamos.

Image for post

 

Modelos (o entidades)

Ahora, vamos a crear el modelo sobre el cual podamos hacer las consultas requeridas. Estas son entidades y para nuestro ejercicio vamos a crear 2: Artista y Álbum (yo creé una carpeta para ubicar los modelos).

public class Artista {
     public System.Guid Id { get; set; }
     public string Nombre { get; set; }
     public string Genero { get; set; }
}

Y para el Álbum

public class Album {
  public System.Guid Id { get; set; }
  public string Nombre { get; set; }
  public Artista Artista { get; set; }
} 

GraphQL Schema

¿Recuerdan la definición previa de Schema? A continuación construimos el schema, aquí creamos una clase que extienda de ObjectGraphType<T> donde T es un tipo entidad de las definidas arriba (recuerden que GraphQL no está amarrado a ningún lenguaje o framework y en particular no requiere entender las clases del CRL). Al igual que con los modelos, puedes crear una carpeta para incluir el schema (Types, Queries).

public class ArtistaType : ObjectGraphType<Artista> {
  public ArtistaType(){
    this.Name = "artista";
    Field(_ => _.Id).Description("Id del autor");
    Field(_ => _.Genero).Description("Genero musical de autor");
    Field(_ => _.Nombre).Description("Nombre del autor");
  }
}  

El AlbumType

public class AlbumType : ObjectGraphType<Album> {
  public AlbumType(){
    this.Name = "album";
    Field(_ => _.Id).Description("Album id");
    Field(_ => _.Nombre).Description("Titulo del album");  
    Field(_ => _.Artista, 
       type: typeof(ArtistaType)).Description("Artista del album");
  }
}  

Y el Query

public class ArtistaQuery : ObjectGraphType{
  public ArtistaQuery(ArtistaService _service){
    Field<ListGraphType<ArtistaType>>(name: "artistas", 
      resolve: context => _service.ListarArtistas());
    Field<ListGraphType<AlbumType>>(name: "albums", 
      resolve: context => _service.ListartAlbums());
    Field<ArtistaType>(name: "artista", 
      arguments: new QueryArguments(
        new QueryArgument<GuidGraphType> { Name = "id" }),
      resolve: context =>
       service.ArtistaPorId(context.GetArgument<System.Guid>"id")));
    Field<ListGraphType<AlbumType>>(
      name: "por_artista", 
      arguments: new QueryArguments(
        new QueryArgument<GuidGraphType> { Name = "id" }),
      resolve: context => 
   _service.AlbumPorArtista(context.GetArgument<System.Guid>"id")));
  }
} 

Observaron el “QueryArgument”, al igual que las api REST es frecuente pasar argumentos a un endpoint en GraphQL. Al definir el argumento en el Schema, la validación sucede de manera automática y cada argumento debe tener nombre y un tipo (aquí encontrarán un listado detallado de los tipos usados en GraphQL https://graphql-dotnet.github.io/docs/getting-started/schema-types/).

En este ejercicio, no estoy usando persistencia en base de datos, estoy jugando con listas en nuestro repositorio, pero idealmente los datos se recuperan desde una base de datos. Aquí les dejo el repositorio:

 public class ArtistaRepository {  private readonly List<Artista> _artistas;
  private readonly List<Album> _albums;  public ArtistaRepository(){
    _artistas = new List<Artista>();
    _albums = new List<Album>();
    _artistas.Add(new Artista { Nombre = "Diomedes Diaz",
      Genero = "Vallenato",Id = Guid.NewGuid()});
    _artistas.Add(new Artista { Nombre = "Freddie Mercury",
      Genero = "Rock", Id = Guid.NewGuid() });
    _albums.Add(new Album{ Nombre = "Mi vida musical",
      Id = Guid.NewGuid(), Artista = _artistas.Where(
        a => a.Nombre.Equals("Diomedes Diaz")).FirstOrDefault() });
    _albums.Add(new Album{Nombre = "Tres Canciones",
      Id = Guid.NewGuid(), Artista = _artistas.Where(
        a => a.Nombre.Equals("Diomedes Diaz")).FirstOrDefault()});
    _albums.Add(new Album{ Nombre = "Mr bad guy",
      Id = Guid.NewGuid(), Artista = _artistas.Where(
        a => a.Nombre.Equals("Freddie Mercury")).FirstOrDefault()});
    _albums.Add(new Album{ Nombre = "Barcelona",
      Id = Guid.NewGuid(), Artista = _artistas.Where(a => a.Nombre.Equals("Freddie Mercury")).FirstOrDefault() });
  }  public List<Artista> TodosLosArtistas() => this._artistas;  public List<Album> TodosLosAlbums() => this._albums;  public Artista ArtistaPorId(Guid id) => 
    this._artistas.Where(a => a.Id.Equals(id)).FirstOrDefault();  public List<Album> AlbumsPorArtista(Guid id) => 
    this._albums.Where(a => a.Artista.Id.Equals(id)).ToList();} 

Y el servicio :

public class ArtistaService {  private readonly ArtistaRepository _artistaRepository;  public ArtistaService(ArtistaRepository repository) {
    _artistaRepository = repository;
  }  public List<Artista> ListarArtistas()=> 
    _artistaRepository.TodosLosArtistas();    
  
  public List<Album> ListartAlbums() => 
    _artistaRepository.TodosLosAlbums();
  
  public List<Album> AlbumPorArtista(Guid artistaId) => 
    _artistaRepository.AlbumsPorArtista(artistaId);
  
  public Artista ArtistaPorId(Guid artistaId) => 
    _artistaRepository.ArtistaPorId(artistaId);
}

Importante: GraphQL usa HTTP como capa de transporte, usamos solo peticiones POST cuando de GraphQL se trata y contra un solo endpoint, lo cual es un contraste con REST que expone un conjunto de urls las cuales exponen un recurso cada una.

Continuemos con el Schema, es una clase que hereda de Schema y de la interfaz ISchema en el namespace GraphQL.Types. Schema y es indispensable para exponer las acciones y datos al consumidor en GraphQL

public class DemoSchema : GraphQL.Types.Schema,GraphQL.Types.ISchema
{
  public DemoSchema(IServiceProvider svc) : base(svc) {
    Query = serviceProvider.GetService<ArtistaQuery>();
  }
}

Como puedes observar aquí resolvemos el query desde el IServiceProvider, lo cual nos indica que ArtistaQuery debe ser inyectado en el IServiceCollection del método ConfigureServices del middleware de NETCore.

¿Recuerdan que les mencioné que GraphQL expone un solo endpoint?, entonces debemos crear ese endpoint que en nuestro caso es una clase que extiende de ControllerBase (básicamente un controlador)

[ApiController, Route("api/gql")]public class QLController : ControllerBase {  private readonly ISchema _schema;
  private readonly IDocumentExecuter _executer;  public QLController(ISchema schema, IDocumentExecuter executer){
    _schema = schema;
    _executer = executer;
  }  [HttpPost]
  public async Task<ExecutionResult> Post([FromBody] GqlQuery query)  { 
    var result = await _executer.ExecuteAsync(_ => {
                    _.Schema = _schema;
                    _.Query = query.Query;
                    _.Inputs = query.Vars?.ToInputs();
    });
    return result;
  }
}

Por favor, toma nota de la ruta “api/gql” que vamos a usar al configurar el middleware. Este controlador recibe vía constructor la interfaz ISchema que contiene las referencias a las queries, mutaciones, suscripciones y la interfaz IDocumentExecuter procesa por completo la solicitud GraphQL (métricas, validaciones, análisis y ejecución).

Solo nos queda inyectar los servicios requeridos en el contenedor de dependencias de NETCore (ConfigureServices)

services.AddTransient<IDocumentExecuter, DocumentExecuter>();
services.AddTransient<IDocumentWriter, DocumentWriter>();
services.AddTransient<ArtistaService>();
services.AddSingleton<ArtistaRepository>();
services.AddTransient<ArtistaQuery>();
services.AddTransient<ArtistaType>();
services.AddTransient<AlbumType>();          
services.AddSingleton<DemoSchema>();
services.AddGraphQL()
  .AddSystemTextJson(cfg => {}, serializerSettings => {})
  .AddDataLoader()
  .AddGraphTypes(typeof(DemoSchema));
services.AddControllers(); 

Y ajustar en Configure (la ruta de la que tomaron nota en la parte de arriba)

app.UseGraphiQl("/graphql", "/api/gql");
app.UseGraphQL<DemoSchema>("/api/gql");

¡Veamos GraphQL en acción!

Image for post

Una vez lanzado el proyecto, ejecutaremos el siguiente query en la interfaz gráfica que nos provee GraphiQL (http(s)://localhost:5000(1)/graphql)

query {
  albums{
   nombre,
    artista{
      nombre, genero
    }   
  }
}
Image for post

Observemos el Schema que nos facilita el construir el Query

Image for post

Como lo ven chicos, es super sencillo arrancar con GraphQL en NETCore, el ejercicio completo está en https://github.com/cheoalfredo/GraphQL-Demo

Te invito a seguirme en mi blog https://cheoalfredo.medium.com/

 

jose.fernandez

Tecnólogo en sistemas de información especialista en desarrollo de software: Desarrollador / Arquitecto / Coach técnico en Ceiba Software House con más de 18 años de experiencia.

2 Commentarios

Déjanos tu comentario

Share via
Copy link