Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 5 de noviembre de 2019
gRPC logo Como seguro sabréis, gRPC lleva ya algún tiempo proponiéndose como sustituto de las APIs HTTP/REST en determinados escenarios, y ASP.NET Core 3.0 incluye de serie soporte para esta tecnología.

En este post echaremos un vistazo a gRPC y su uso en la nueva versión del framework.

¿Qué es gRPC?

Se trata de un marco de trabajo para la implementación de llamadas a procedimientos remotos (vaya, el RPC de toda la vida), actualizado y basado en estándares abiertos como HTTP/2 y Protobuf y que, a diferencia de otras opciones, proporciona soporte directo para autenticación, streaming, serialización binaria, cancelación o timeouts, entre otras características.

Aunque inicialmente fue desarrollado por Google para su uso interno, hoy en día es un proyecto perteneciente a la Cloud Native Computing Foundation, una organización que promueve estándares y tecnologías abiertas, interoperables e independientes de fabricantes o proveedores de servicios. Hoy en día dispone de implementaciones en un gran número de lenguajes y plataformas.

Como curiosidad, en la versión 1.0, el término gRPC procedía del acrónimo recursivo "gRPC Remote Procedure Call", pero el significado de la "g" inicial va cambiando el cada release: "Good Remote Procedure Call", "Glamorous Remote Procedure Call", etc. Podéis ver la lista completa aquí

Respecto a las APIs REST tradicionales, basadas principalmente en el uso de HTTP/1.x y JSON, el uso de gRPC permite prestar y consumir servicios de forma mucho más potente y eficiente gracias a la estructura y menor tamaño de sus mensajes y al uso de protocolos modernos, que ofrecen menor latencia y posibilidades, antes impensables, como el streaming bidireccional o el multiplexado de conexiones.

Pero claro, también hay algunos aspectos negativos. El principal en este momento sería la falta de soporte universal por parte de algunos tipos de servidor y de los navegadores, lo que limita su uso sólo a algunos escenarios (comunicación entre servidores, microservicios, custom client/server, o similares) o hace necesario el uso de gateways que intermedien entre los navegadores y los endpoints gRPC. También, dado el uso de serialización binaria y empaquetado de mensajes, no será sencillo leerlos o crearlos de forma directa, como hacíamos con APIs HTTP/JSON usando herramientas como Fiddler o Postman.

.NET Core 3 ofrece herramientas tanto para implementar servicios gRPC (lado servidor) como para consumirlos (lado cliente).
Ojo: en este momento, Azure App Service no soporta todavía el alojamiento de servicios gRPC. Si esto es un must, deberíais seguir de cerca el issue donde se está comentando el asunto.

Implementación de servicios gRPC en ASP.NET Core (lado servidor)

Podemos crear muy fácilmente un proyecto gRPC desde Visual Studio, seleccionando este tipo de proyecto en el primer paso del asistente:
Creación de proyecto gRPC
Como es habitual, también podríamos conseguir lo mismo utilizando la línea de comandos .NET Core, ejecutando dotnet new grpc.
Este tipo de proyecto es una aplicación ASP.NET Core estándar a la que se ha añadido el paquete NuGet Grpc.AspNetCore, que es el que proporciona el soporte para gRPC. También encontraremos en la clase Startup código para registrar los servicios y mapear un endpoint gRPC hacia un servicio incluido en la plantilla por defecto, llamado GreeterService:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddGrpc();
    }

    public void Configure(IApplicationBuilder app)
    {
        ...
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGrpcService<GreeterService>();
            ...
        });
    }
}
Obviamente, también podríamos haber partido de una aplicación ASP.NET Core, añadirle el correspondiente paquete NuGet e introducir el código de inicialización. El resultado sería el mismo, pero si creamos el proyecto tendremos ya implementado un servicio de ejemplo.

Definición de servicios

A diferencia de servicios API HTTP convencionales, los servicios gRPC parten de la definición de un contrato, o interfaz, especificado utilizando sintaxis protobuf en archivos con extensión .proto.

La verdad es que incluso sin haberlo visto antes, la estructura del servicio definida en el .proto se puede leer y comprender muy fácilmente. Como muestra, en el siguiente código es el contenido del archivo Protos/Greet.proto incluida por defecto en los proyectos gRPC:
syntax = "proto3";

option csharp_namespace = "GrpcServiceDemo";

package Greet;

// Definición del servicio
service Greeter {
  // Método para el envío de un saludo
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// Estructura del mensaje de entrada (el nombre del solicitante):
message HelloRequest {
  string name = 1;
}

// Estructura del mensaje de respuesta (el texto del saludo):
message HelloReply {
  string message = 1;
}
Como se puede intuir, las primeras líneas establecen parámetros básicos, como la versión de Protocol Buffer en uso o el namespace de las clases que serán generadas a partir de esta definición. Más abajo, la definición del servicio service permite especificar los métodos que serán invocables de forma remota, a la vez que define el tipo de dato que recibe y retorna cada uno de ellos.

Dichos tipos de datos son detallados más abajo en estructuras message que incluyen información sobre los campos que contienen, sus identificadores únicos (el número que aparece junto a cada campo), y su tipología (string, bool, int32, etc.) Podéis consultar la guía completa del lenguaje en la documentación oficial de protobuf.

Por cada archivo .proto, el tooling incluido en el paquete NuGet que hemos instalado en el proyecto generará automáticamente una clase que usaremos como base para implementar nuestros métodos remotos.

Ojo: si vuestro nombre de usuario en Windows contiene caracteres especiales o espacios, la compilación fallará. Echad un vistazo a este post.

En el ejemplo anterior, se generará una clase llamada Greeter.GreeterBase que contendrá implementaciones vacías de los métodos especificados en los contratos; lo único que tendremos que hacer es heredar de esta clase y sobrescribir sus métodos, introduciendo en ellos nuestra lógica de proceso de invocaciones:
public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply
        {
            Message = "Hello " + request.Name
        });
    }
}
Fijaos que en la implementación estamos utilizando clases tipadas como HelloRequest y HelloReply, que también han sido generadas de forma automática, para representar los parámetros de entrada y los resultados a retornar, respectivamente.
Nota: Estas clases son generadas en la carpeta /obj en cada compilación y no son incluidas en el proyecto o en los repositorios de código. Pero no os preocupéis, porque tampoco vais a necesitar acceder a ellas directamente.
Finalmente, será la clase que implementa el servicio, en nuestro ejemplo GreeterService, la que se registrará como endpoint en la clase de inicialización de la aplicación Startup.
public void Configure(IApplicationBuilder app)
{
    ...
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<GreeterService>();
        ...
    });
}

Ejecución del servidor

Podemos ejecutar nuestro proyecto gRPC como cualquier otro, ya que no hay diferencia con una aplicación ASP.NET Core de las que estamos acostumbrados a utilizar. De hecho, como seguro podréis intuir, es totalmente posible que la misma aplicación ASP.NET Core albergue servicios gRPC al mismo tiempo que otros tipos de endpoint como MVC o Razor Pages.

Pero eso sí, un aspecto importante a tener en cuenta es que gRPC utiliza necesariamente HTTP/2 y TLS. En el entorno de desarrollo estos dos aspectos están configurados por defecto y no tendremos que hacer nada, pero en la documentación oficial se detallan algunos pasos extra necesarios para la puesta en marcha de los servicios gRPC en entornos de producción.

En cualquier caso, una vez puesto en marcha necesitaremos un cliente gRPC para poder invocar a nuestros procedimientos remotos.

Consumo de servicios gRPC

Podemos consumir servicios gRPC desde cualquier tipo de aplicación .NET Standard 2.1.

Añadir servicio conectado

Para ello, sin duda lo más sencillo es utilizar las herramientas de Visual Studio para generar un proxy que nos permita acceder a los servicios de forma tipada. Esto lo conseguimos abriendo el menú contextual sobre la carpeta References del proyecto, y seleccionando la opción Add connected service.

Tras ello, en la pestaña Service References pulsamos la opción correspondiente para añadir una referencia a un servicio gRPC, introduciendo en el cuadro de diálogo la ruta hacia el archivo .proto, que especifica el contrato del servicio, e indicando en este caso que deseamos generar el código para acceder al mismo, es decir, el código para un cliente del servicio.

Añadir referencia a servicio gRPC

A partir de este momento tendremos en nuestro proyecto la referencia añadida. Desde ese mismo punto podemos examinar la clase C# generada, editar la referencia, o eliminarla cuando ya no la necesitemos.

Referencia a servicio añadida

Además de generar la clase, este proceso incluirá en el proyecto un vínculo hacia el .proto original (es decir, no copia físicamente el archivo sino un "atajo" hacia él), así como los paquetes NuGet necesarios para que todo funcione, en caso de que no existieran ya:
  • Google.Protobuf
  • Grpc.Net.ClientFactory
  • Grpc.Tools
Si en lugar de Visual Studio preferimos utilizar la línea de comandos de .NET Core (CLI), lo único que tendremos que hacer es incluir de forma manual los paquetes NuGet anteriores, y traer de alguna forma el archivo .proto al proyecto, bien sea copiándolo o bien añadiendo manualmente un vínculo al archivo original, como se muestra en la siguiente porción de .csproj:
<ItemGroup>
  <Protobuf Include="..\GrpcServiceDemo\Protos\greet.proto" GrpcServices="Client">
    <Link>Protos\greet.proto</Link>
  </Protobuf>
</ItemGroup>
En cualquier caso, el tooling generará una clase estática con el nombre del servicio especificado en el archivo .proto, en la que existirá una clase instanciable con el nombre del mismo seguido de la palabra Client. Por ejemplo, en el caso del servicio Greeter que hemos visto anteriormente, podemos instanciar nuestro cliente haciendo un new Greeter.GreeterClient(...). Esta clase actuará como proxy para realizar las invocaciones al procedimiento remoto.

Sin embargo, para instanciar un cliente necesitamos disponer previamente de un canal gRPC, que básicamente es la tubería a través de la cual realizaremos las llamadas. Al crearlo, especificaremos la dirección del servidor que aloja los servicios y, opcionalmente, settings para modificar el comportamiento por defecto del mismo.

La creación de un canal es tan sencilla como se muestra a continuación:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
Como indican en la documentación oficial, es importante tener en cuenta que la creación de estos objetos GrpcChannel puede ser costosa, y conviene que sean reutilizados entre distintos servicios o clientes. De hecho, el framework proporciona una factoría de clientes gRPC muy parecida a la que utilizamos para reutilizar instancias de HttpClient.
Con el canal creado ya podemos instanciar nuestra clase cliente e invocar el método remoto, enviándole los parámetros que espera recibir, que en este caso serán de tipo HelloRequest. Observad que la respuesta a la llamada es también tipada, y en nuestro ejemplo recibiremos objetos HelloReply, según lo especificado por el contrato protobuf:
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);

var request = new HelloRequest() { Name = "John" };
HelloReply result = await client.SayHelloAsync(request);

Console.WriteLine(result.Message); // Hello John

En resumen

Como hemos visto, el tooling y componentes proporcionados por .NET Core 3 ocultan gran parte de la complejidad real de la implementación de gRPC tanto en cliente como en servidor, convirtiéndolo en lago casi trivial para los desarrolladores.

Muy resumidamente, la cosa consiste en:
  • En el lado servidor:
    • Definir el contrato de los servicios, procedimientos y tipos de mensaje en un archivo .proto.
    • Heredar de las clases autogeneradas y sobrescribir sus métodos con el código personalizado de los servicios.
  • En el lado cliente:
    • Añadir el contrato del servicio (archivo .proto) al proyecto.
    • Utilizar las clases generadas para invocar los procedimientos remotos.
Aunque aún faltan algunos aspectos por afinar, como el soporte desde algunos clientes y servidores, la verdad es que gRPC se está posicionando como una alternativa bastante interesante para el desarrollo de APIs en las que sea fundamental el rendimiento u otras de las interesantes características proporcionadas por esta tecnología.

Y dicho esto... mmmmm.... cómo recuerda esta forma de trabajar a los tiempos en los que los Web Services XML eran la panacea, ¿verdad? ;)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!