gRPC e la comunicazione tra servizi in C#
Le API REST basate su HTTP e JSON, viste nella terza serie, sono un'ottima scelta per molti scenari, ma quando i servizi devono comunicare tra loro con la massima efficienza esiste un'alternativa più performante: gRPC. Sviluppato originariamente da Google, gRPC è un framework di chiamata a procedura remota che usa un formato binario compatto e il protocollo HTTP/2, offrendo prestazioni elevate e contratti fortemente tipizzati. In questo articolo vediamo come definire un servizio, implementarlo e consumarlo, e quali vantaggi porta nella comunicazione tra microservizi.
Che cos'è gRPC
L'idea alla base di gRPC è la chiamata a procedura remota: invocare un metodo su un server come se fosse locale, mentre il framework si occupa di serializzare gli argomenti, trasmetterli, eseguire il metodo remoto e restituire il risultato. Rispetto a REST, gRPC si distingue per tre elementi: il formato binario Protocol Buffers al posto del JSON testuale, il protocollo HTTP/2 che consente il multiplexing e lo streaming, e un contratto formale che genera automaticamente il codice sia per il server sia per il client, garantendo coerenza.
Protocol Buffers e il file .proto
Il cuore di gRPC è il file di definizione con estensione .proto, scritto nel linguaggio Protocol Buffers. Questo file descrive il servizio, i metodi disponibili e la struttura dei messaggi scambiati. Funge da contratto unico e condiviso: da esso si genera il codice per entrambe le estremità della comunicazione.
syntax = "proto3";
option csharp_namespace = "GrpcDemo";
// Definizione del servizio e dei suoi metodi
service ProductService {
rpc GetProduct (ProductRequest) returns (ProductReply);
}
// Il messaggio di richiesta
message ProductRequest {
int32 id = 1;
}
// Il messaggio di risposta
message ProductReply {
int32 id = 1;
string name = 2;
double price = 3;
}
I numeri assegnati ai campi non sono valori, ma identificatori univoci usati nella codifica binaria. È questa rappresentazione compatta, unita alla generazione automatica del codice, a rendere gRPC efficiente e sicuro rispetto ai tipi.
Implementare il servizio
A partire dal file .proto, gli strumenti di gRPC generano una classe base astratta per il servizio. Si implementa il servizio derivando da questa classe e ridefinendo i metodi definiti nel contratto, applicando le tecniche di override viste nella prima serie. Il metodo riceve la richiesta tipizzata e restituisce la risposta tipizzata.
using Grpc.Core;
class ProductServiceImpl : ProductService.ProductServiceBase
{
// Ridefinizione del metodo definito nel contratto
public override Task<ProductReply> GetProduct(
ProductRequest request, ServerCallContext context)
{
var reply = new ProductReply
{
Id = request.Id,
Name = "Cuffie",
Price = 49.99
};
return Task.FromResult(reply);
}
}
Il servizio si registra nell'applicazione ASP.NET Core in modo analogo ai controller, mappandolo nella pipeline delle richieste.
builder.Services.AddGrpc();
var app = builder.Build();
// Espone il servizio gRPC
app.MapGrpcService<ProductServiceImpl>();
app.Run();
Consumare il servizio dal client
Il client usa il codice generato dallo stesso file .proto. Crea un canale verso l'indirizzo del server e un client tipizzato, poi invoca i metodi remoti come se fossero locali, con piena sicurezza sui tipi di argomenti e risultati.
using Grpc.Net.Client;
// Canale verso il server gRPC
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
// Client tipizzato generato dal contratto
var client = new ProductService.ProductServiceClient(channel);
// Chiamata remota che appare come una normale chiamata a metodo
var reply = await client.GetProductAsync(new ProductRequest { Id = 1 });
Console.WriteLine($"{reply.Name}: {reply.Price}");
Le modalità di streaming
Grazie a HTTP/2, gRPC supporta lo streaming oltre alla semplice richiesta-risposta. Esistono quattro modalità, che coprono scenari diversi:
- Unario: una richiesta, una risposta, come nell'esempio precedente.
- Server streaming: una richiesta, un flusso di risposte, utile per inviare progressivamente molti dati.
- Client streaming: un flusso di richieste, una risposta, adatto a caricamenti o aggregazioni.
- Bidirezionale: entrambe le parti inviano flussi indipendenti, per comunicazione continua in tempo reale.
Nel file .proto, lo streaming si dichiara con la parola chiave stream davanti al tipo di messaggio.
service PriceService {
// Il server invia un flusso continuo di aggiornamenti
rpc SubscribePrices (PriceRequest) returns (stream PriceUpdate);
}
gRPC rispetto a REST
La scelta tra gRPC e REST dipende dal contesto. gRPC eccelle nella comunicazione interna tra microservizi, dove le prestazioni, i contratti rigorosi e lo streaming portano vantaggi concreti. REST, con il suo formato testuale e l'ampio supporto, resta preferibile per le API pubbliche destinate a client eterogenei, in particolare i browser web, il cui supporto a gRPC è limitato e richiede componenti aggiuntivi. Molte architetture usano entrambi: REST verso l'esterno, gRPC tra i servizi interni.
Conclusione
Abbiamo introdotto gRPC come framework ad alte prestazioni per la comunicazione tra servizi, basato su Protocol Buffers e HTTP/2. Abbiamo definito un contratto in un file .proto, implementato il servizio derivando dalla classe base generata, e consumato il servizio da un client tipizzato. Abbiamo esplorato le quattro modalità di streaming e collocato gRPC rispetto a REST. Nel prossimo e ultimo articolo di questa sezione ci occuperemo della sicurezza di rete, con TLS e l'autenticazione.