La .NET CONF 2024 acaba de terminar. Descubre las NOVEDADES de .NET 9 junto a los CAMBIOS que trae C# 13.
Puedes ver el contenido de este vídeo junto con su curso en el modo vídeo (similar a Udemy) si pulsas aquí.

gRPC y C#: Revoluciona la Comunicación entre servicios

23 Aug 2024 15 min (0) Comentarios

Cuando hablamos de APIs el 90% de la gente se refiere a una API REST Que hoy en día es la forma más común y popular de comunicar aplicaciones entre sí, ya sea desde la interfaz de usuario, desde una aplicación de terceros o desde otro sistema. 

 

Pero Rest no es la única forma, anteriormente vimos GraphQL y sus características y hoy, vamos a ver otra forma de comunicarnos, con gRPC.

 

 

 

 

1 - Qué es gRPC?

 

Si nos basamos en la descripción oficial (en su web) de gRPC podemos decir que es un framework universal opensource para RPC de alto rendimiento. 

 

Vamos a diseccionar un poco lo que quiere decir esta frase;

 

  • Decimos que es de alto rendimiento porque la forma en la que serializamos y deserializamos la comunicación o las llamadas.
  • Lo de framework universal es porque es agnóstico del lenguaje, funciona con C#, Java, C++, Python, etc.
  • RPC son las siglas de Remote Procedure Call (Llamada a procedimiento remoto), que viene significando que, un servicio o programa va a solicitar que otro realice cierta operación. Es el equivalente a una request en el mundo REST al que posiblemente estés más acostumbrado.

Y la g? Bueno si vas a la web, te dirá que la web es como la L de linux, que la g significa gRPC.

La realidad no confirmada es que la g significa google porque fue la empresa que lo creó.

 

 

1.1 - Características de gRPC

 

Cuando trabajamos con gRPC vamos a estar hablando de comunicación en forma binaria. Ya no enviamos texto como hacemos con Rest o incluso GraphQL, sino que enviamos binario, lo que implica que los paquetes que enviamos a través de la red van a ser lo más pequeños posible.

 

La comunicación en gPRC se basa en contratos, que quiero decir con esto? Muy sencillo, para poder serializar y deserializar correctamente tanto el cliente que consume el servicio con el servidor que genera necesitan tener el mismo contrato o la deserialización va a fallar.

Estos contratos son los ficheros de definición de gRPC los cuales contienen la extensión .proto, y como menciono, hay que mantenerlos actualizados cuando sucede un cambio. 

 

Necesitamos HTTPS, gRPC funciona solo en llamadas seguras utilizando https, se le puede poner autenticación, pero esa parte es opcional, la parte obligatoria es que necesitamos HTTPS.

Nota: funciona sobre HTTP/2;

 

Streaming bi-direccional, Si estás acostumbrado a REST todo funciona con Request/Response. gRPC tiene la capacidad de enviar y recibir datos de forma simultánea, por ejemplo, no es necesario que una request termine para empezar a devolver datos.

 

Un ejemplo muy sencillo es en una videoconferencia, todos los participantes están enviando y recibiendo datos de forma simultánea.

 

 

2 - Dónde utilizar gRPC? 

 

Si nos basamos en el primer punto, podemos asumir que debemos utilizar gRPC en todas partes, no? Es más rápido y más ligero que cualquier otra alternativa, así que tendría sentido.

 

La realidad es que no. Los motivos son sencillos, desde el front end on en Javascript no es tan fácil trabajar con gRPC como con las alternativas, así que lo más común es dejarlo de lado, mantener el contrato por parte de los de front end no es algo que les apasione, ya que cuando hacen una llamada no la pueden ver con sus ojos que están mandando al servidor cuando están desarrollando 😅.

 

Además de que serializar a binario es más costoso que a json, que teniendo en cuenta que muchos de nuestros clientes van a usar ordenadores de hace 15 o 20 años, pues no sale a cuenta. 

 

En mi opinión, la comunicación front end - Back end debe ser hecha con REST/GraphQL.

 

Donde gRPC brilla es en la comunicación entre sistemas

 

Si tienes una api que llama a otra API, o a otro servicio, ahí es donde gRPC brilla y lo hace muy bien. 

explicación grpc

En resumen, a todas partes donde el usuario no tiene acceso.

Como puedes ver en esa imagen tenemos una llamada entre la api de orders y la api de products, cuando acabamos de decir que si el usuario tiene acceso usamos REST o grpahql, si y no. Usamos REST para la parte donde el usuario accede, pero si tenemos que consultar algo desde otra API, podemos usar gRPC. 

 

Nota: en esta imagen tenemos un sistema completamente acoplado, lo que en otras palabras podríamos llamar un monolito distribuido. Si quieres implementar sistemas distribuidos en la forma correcta te recomiendo que mires mi curso gratuito sobre sistemas distribuidos.

 

Cuando se da el caso de que una api es pública a  los usuarios y de acceso interno como la del ejemplo no siempre se debe hacer gRPC, hay que evaluar los costos de desarrollo y mantenimiento en comparación al beneficio. El punto principal que quería decir es que ambos sistemas, tanto una API REST como gRPC son completamente compatibles en una misma aplicación.

 

Como nota adicional, Linkedin tiene un artículo en su blog de ingeniería sobre cómo migraron de REST a gRPC y como mejoró la latencia, o en otras palabras el tiempo de respuesta, en un 60%.

Como efecto secundario de ahorrar un 60% en latencia, ahorras en costes, no solo de transferencia pero de CPU, memoria, etc, que en el mundo serverless que vivimos hoy en día es muy importante. 

 

 

3 - contratos en gRPC

 

Los contratos en gRPC se merecen un punto individual, pese a que los podría meter en la sección donde vamos a ver la implementación al ser agnósticos al lenguaje, creo que es importante separarlos. 

 

Este es un ejemplo de contrato que puedes ver en la plantilla de C# de gRPC:

syntax = "proto3";

option csharp_namespace = "GrpcService1";

package greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

Esto es un contrato, en otras palabras, el fichero protobuf, el cual define la interfaz (y de ahí llamarlo contrato) que tanto nuestro servidor como el cliente van a utilizar y este fichero es completamente independiente del lenguaje.

 

 

Ahora vamos a diseccionar este fichero.

 

  • La primera línea define el esquema que el fichero va a utilizar, en nuestro caso actual es proto3, y es básicamente la versión, no lo tienes que cambiar.
  • La siguiente línea option csharp_namespace = "GrpcService1";, define el namespace donde el código generado por el fichero pronto va a estar ubicado en nuestro código, normalmente elegiremos un namespace que esté dentro de nuestro servidor y es exclusiva de C#. Otros lenguajes pueden utilizar otras opciones diferentes, en Ruby por ejemplo es ruby_package GrpcService1.
  • La línea package greet está definida para organizar protobuf, no tiene nada que ver con el lenguaje que vaya a implementar  protobuf.
  • La siguiente sección, define el servicio y los métodos o funciones que dicho fichero va a generar y soportar:
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

Como puedes ver, contiene tanto la request como la response especificados para los que tenemos la definición de los mismos abajo. 

  • Los números =1 que puedes ver en los mensajes identifican cada elemento que será utilizado luego en la (de)serialización. Cuando tengas múltiples propiedades simplemente ponlas en orden porque no tiene ningún sentido ir saltando números. 

 

 

4 - Implementar gRPC en C#

 

En el post de hoy vamos a ver como implementar tanto el lado cliente como el lado servidor utilizando C#. 

 

4.1- Implementar servidor gRCP en C# 

 

En este post vamos a ver el lado servidor de gRPC con el proyecto de plantilla que nos da visual studio / rider por defecto. Se puede poner gRPC a un proyecto existente sin ningún problema. En cualquier caso vamos a explicar todo lo que necesitamos. 

 

Lo primero es el paquete NuGet de Grpc.AspnetCore.

 

Una vez esto vamos a crear nuestro fichero proto, que en el caso de la plantilla es el fichero greet.proto que hemos explicado en el punto anterior y normalmente irán dentro de una carpeta llamada protos. 

 

Con esto únicamente no podemos hacer nada. Ya que directamente no podemos referenciar un fichero .proto. Aquí es donde entra el paquete que hemos añadido, ya que ahora, cuando hagamos una build se nos va a generar un fichero de c# que contiene tanto los tipos como los métodos que hemos incluido en el fichero proto.

protobuf build c#

Este fichero es autogenerado y pese a que por poder puedes editar, no lo hagas porque la siguiente vez que hagas la build va a volverse a modificar. 

 

No lo he dicho anteriormente, pero este fichero funciona de una forma un poco diferente al resto de ficheros, ya que tiene su propia etiqueta en el csproj, lo cual es importante, porque en esta etiqueta definiremos si la aplicación va a ser servidor o cliente:

<ItemGroup>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Server"/>
</ItemGroup>

 

Una vez esto está claro, ese fichero c# que se genera de forma automática contiene no sólo los tipos y los métodos. Estos métodos están dentro de una clase abstracta llamada GreeterBase la cual basa su nombre en service de nuestro fichero proto.

 

Lo importante es que esta clase tiene todos los métodos que hemos especificado, pero sus implementaciones lanzan excepciones:

/// <summary>Base class for server-side implementations of Greeter</summary>
[grpc::BindServiceMethod(typeof(Greeter), "BindService")]
public abstract partial class GreeterBase
{
    /// <summary>
    /// Sends a greeting
    /// </summary>
    /// <param name="request">The request received from the client.</param>
    /// <param name="context">The context of the server-side call handler being invoked.</param>
    /// <returns>The response to send back to the client (wrapped by a task).</returns>
    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
    public virtual global::System.Threading.Tasks.Task<global::GrpcService1.HelloReply> SayHello(global::GrpcService1.HelloRequest request, grpc::ServerCallContext context)
    {
    throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
    }
}

 

Pero esto no nos tiene que importar, porque lo que vamos a hacer es crear una clase e implementar dicha clase abstracta, junto con los métodos. En esta clase, podemos inyectar cualquier servicio que queramos o realizar las operaciones que creamos necesarias. En resumen, es el punto de entrada a tu aplicación cuando utilizas gRPC. 

public class GreeterService : Greeter.GreeterBase
{
    private readonly ILogger<GreeterService> _logger;

    public GreeterService(ILogger<GreeterService> logger)
    {
        _logger = logger;
    }

    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply
        {
            Message = "Hello " + request.Name
        });
    }
}

 

Y como prácticamente cada funcionalidad que utilizamos en .NET, debemos incluir gRPC dentro del contenedor de dependencias e incluir el servicio en la request pipeline:

builder.Services.AddGrpc();
...
app.MapGrpcService<GreeterService>();

 

Ahora, nuestra aplicación está lista para recibir llamadas, pero no todo tipo de llamadas, únicamente llamadas gRPC ya que solo tenemos habilitado Http2 en la configuración:

"Kestrel": {
    "EndpointDefaults": {
        "Protocols": "Http2"
    }
}

 

Si lo que queremos es recibir llamadas en una api REST  “normal”, debermos cambiar el protocolo por Http1AndHttp2 de esa forma nuestra API soportará tanto llamadas gRPC como HTTP estándar.

 

 

 

4.2 - Consumir un servicio gRPC desde C#

 

Como consumidor del servicio podemos utilizar cualquier clase de proyecto, puede ser un proyecto en C# o en cualquier lenguaje, ya que lo que vamos a hacer es utilizar el fichero proto que hemos creado en la aplicación servidor. 

 

Como sabemos de puntos anteriores el fichero proto es conocido tanto por la aplicación servidor, que será quien lo crea como en la aplicación cliente. Para nuestro caso vamos a crear una API, que tiene un Endpoint el cual simplemente llamará a nuestro servicio gRPC que acabamos de crear. 

 

Por ahora simplemente vamos a crear un endpoint el cual se llamará get-greetings, y es donde haremos la llamada.:

app.MapGet("/get-greetings", ()=> "TODO");

 

Para preparar nuestro código, debemos hacer dos cosas.

Primero de todo debemos instalar varios paquetes nuget: 

  • Grpc.Net.Client
  • Google.Protobuf
  • Grpc.Tools

 

Después debemos copiar el fichero proto desde la aplicación servidor a esta nueva aplicación y nos debemos asegurar en el csproj que el fichero esta marcado como Client:

<ItemGroup>
    <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

Ya que este cambio es importante a la hora de acceder a la información que nos genera la build. Porque, igual que en el lado del servidor nos crea un servicio base donde crear nuestra lógica, en el cliente nos crea un cliente de forma automática.

 

Para utilizar dicho cliente, en nuestro endpoint, debemos hacer varios pasos, primero crear un GrpcChannel el cual contiene la url donde nuestro servicio está desplegado.

Con ese channel, utilizamos el código generado automáticamente a través de nuestro fichero proto para instanciar el cliente y finalmente  llamamos al método del cliente gRPC que queremos invocar:

app.MapGet("/get-greetings", () =>
{
    GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:7171");
    Greeter.GreeterClient client = new Greeter.GreeterClient(channel);
    HelloReply reply = client.SayHello(new HelloRequest { Name = "example name" });
    return reply.Message;
});

 

Y con eso ya funciona (siempre y cuando realices el siguiente punto del blog, 4.2.1):

resultado grpc

 

4.2.1 - Test gRPC en local con certificados

 

En los puntos anteriores he mencionado que gRPC funciona únicamente a través de https, pero no solo eso, al usar HTTP/2 necesitas tener TLS habilitado, lo cual es un engorro ya que necesitas un certificado válido.

 

Hay dos formas de solucionar esto en local, la primera es creando un handler HTTP que se salte la validación del certificado:

var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = 
    HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; 👈

var channel = GrpcChannel.ForAddress("https://localhost:7171",
    new GrpcChannelOptions { HttpHandler = handler }); 👈
var client = new Greet.GreeterClient(channel); 

Obviamente esta opción es un poco arriesgada, por si se te olvida quitarlo antes de ir a producción. 

 

La segunda es confiando en el certificado local de dotnet, que lo hacemos únicamente corriendo el siguiente comando:

dotnet dev-certs https --trust

Y es la opción que, en mi opinión, debemos utilizar cuando trabajamos en local.

 

 

4.2.2 - Incluir gRPC en el contenedor de dependencias

 

Nuestro endpoint es un poco feo, instanciando el channel y de ahí creando el cliente, sinceramente, no parece muy elegante. Lo que podemos hacer es incluir este cliente en el contenedor de dependencias.  Para ello lo que debemos hacer es incluir grpc en dicho contenedor y posteriormente incluir el cliente: 

builder.Services.AddGrpc();
builder.Services.AddGrpcClient<Greeter.GreeterClient>(x =>
{
    x.Address = new Uri("https://localhost:7171");
});

 

Y en el endpoint (o caso de uso) únicamente inyectamos el cliente: 

app.MapGet("/get-greetings", (Greeter.GreeterClient client) =>
{
    HelloReply reply = client.SayHello(new HelloRequest { Name = "example name" });
    return reply.Message;
});

 

Si te fijas, cuando hemos añadido el cliente al contenedor de dependencias hemos utilizado AddGrpcclient<T> lo que significa que podemos tener tantos clientes como queramos. 

 


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café