gRPC vs. REST: Guida introduttiva al miglior protocollo API

Pubblicato: 2022-07-22

Nel panorama tecnologico odierno, la maggior parte dei progetti richiede l'uso di API. Le API collegano la comunicazione tra servizi che possono rappresentare un unico sistema complesso ma possono anche risiedere su macchine separate o utilizzare più reti o linguaggi incompatibili.

Molte tecnologie standard soddisfano le esigenze di comunicazione tra i servizi dei sistemi distribuiti, come REST, SOAP, GraphQL o gRPC. Mentre REST è un approccio preferito, gRPC è un degno contendente, che offre prestazioni elevate, contratti digitati e strumenti eccellenti.

Panoramica REST

Il trasferimento dello stato rappresentativo (REST) ​​è un mezzo per recuperare o manipolare i dati di un servizio. Un'API REST è generalmente costruita sul protocollo HTTP, utilizzando un URI per selezionare una risorsa e un verbo HTTP (ad esempio, GET, PUT, POST) per selezionare l'operazione desiderata. I corpi di richiesta e risposta contengono dati specifici dell'operazione, mentre le loro intestazioni forniscono metadati. Per illustrare, diamo un'occhiata a un esempio semplificato di recupero di un prodotto tramite un'API REST.

Qui, richiediamo una risorsa prodotto con un ID 11 e indirizziamo l'API a rispondere in formato JSON:

 GET /products/11 HTTP/1.1 Accept: application/json

Data questa richiesta, la nostra risposta (intestazioni irrilevanti omesse) potrebbe essere simile a:

 HTTP/1.1 200 OK Content-Type: application/json { id: 11, name: "Purple Bowtie", sku: "purbow", price: { amount: 100, currencyCode: "USD" } }

Sebbene JSON possa essere leggibile dall'uomo, non è ottimale se utilizzato tra i servizi. La natura ripetitiva del riferimento ai nomi delle proprietà, anche se compressi, può portare a messaggi gonfi. Diamo un'occhiata a un'alternativa per affrontare questa preoccupazione.

Panoramica di gRPC

gRPC Remote Procedure Call (gRPC) è un protocollo di comunicazione open source, basato su contratto e multipiattaforma che semplifica e gestisce la comunicazione tra servizi esponendo una serie di funzioni a client esterni.

Basato su HTTP/2, gRPC sfrutta funzionalità come lo streaming bidirezionale e il Transport Layer Security (TLS) integrato. gRPC consente una comunicazione più efficiente attraverso payload binari serializzati. Per impostazione predefinita, utilizza i buffer di protocollo come meccanismo per la serializzazione dei dati strutturati, in modo simile all'uso di JSON da parte di REST.

A differenza di JSON, tuttavia, i buffer di protocollo sono più di un formato serializzato. Includono altre tre parti principali:

  • Un linguaggio di definizione del contratto che si trova nei file .proto (seguiremo proto3, l'ultima specifica del linguaggio del buffer del protocollo).
  • Codice funzione di accesso generato
  • Librerie di runtime specifiche della lingua

Le funzioni remote disponibili su un servizio (definite in un file .proto ) sono elencate all'interno del nodo del servizio nel file buffer del protocollo. In qualità di sviluppatori, possiamo definire queste funzioni ei loro parametri utilizzando il sistema di tipi avanzati dei buffer di protocollo. Questo sistema supporta vari tipi numerici e di data, elenchi, dizionari e valori nullable per definire i nostri messaggi di input e output.

Queste definizioni di servizio devono essere disponibili sia per il server che per il client. Sfortunatamente, non esiste un meccanismo predefinito per condividere queste definizioni oltre a fornire l'accesso diretto al file .proto stesso.

Questo file .proto di esempio definisce una funzione per restituire una voce di prodotto, dato un ID:

 syntax = "proto3"; package product; service ProductCatalog { rpc GetProductDetails (ProductDetailsRequest) returns (ProductDetailsReply); } message ProductDetailsRequest { int32 id = 1; } message ProductDetailsReply { int32 id = 1; string name = 2; string sku = 3; Price price = 4; } message Price { float amount = 1; string currencyCode = 2; }
Snippet 1: Definizione del servizio ProductCatalog

La digitazione rigorosa e l'ordinamento dei campi di proto3 rendono la deserializzazione dei messaggi notevolmente meno gravosa rispetto all'analisi di JSON.

Confronto tra REST e gRPC

Per ricapitolare, i punti più significativi nel confronto tra REST e gRPC sono:

RIPOSO gRPC
Multipiattaforma
Formato messaggio Personalizzato ma generalmente JSON o XML Buffer di protocollo
Dimensione del carico utile del messaggio Medio grande Piccolo
Complessità di elaborazione Superiore (analisi del testo) Inferiore (struttura binaria ben definita)
Supporto del browser Sì (nativo) Sì (tramite gRPC-Web)

Laddove sono previsti contratti meno rigidi e frequenti aggiunte al carico utile, JSON e REST sono ottime soluzioni. Quando i contratti tendono a rimanere più statici e la velocità è della massima importanza, gRPC generalmente vince. Nella maggior parte dei progetti su cui ho lavorato, gRPC si è dimostrato più leggero e più performante di REST.

Implementazione del servizio gRPC

Costruiamo un progetto semplificato per esplorare quanto sia semplice adottare gRPC.

Creazione del progetto API

Per iniziare, creeremo un progetto .NET 6 in Visual Studio 2022 Community Edition (VS). Selezioneremo il modello di servizio gRPC ASP.NET Core e nomineremo sia il progetto (usare InventoryAPI ) sia la nostra prima soluzione al suo interno ( Inventory ) .

Una finestra di dialogo "Configura il tuo nuovo progetto" all'interno di Visual Studio 2022. In questa schermata abbiamo digitato "InventoryAPI" nel campo Nome progetto, abbiamo selezionato "C:\MyInventoryService" nel campo Posizione e digitato "Inventario" nel campo Nome soluzione . Abbiamo lasciato deselezionato "Posiziona soluzione e progetto nella stessa directory".

Ora scegliamo il . Opzione NET 6.0 (supporto a lungo termine) per il nostro framework:

Una finestra di dialogo Informazioni aggiuntive all'interno di Visual Studio 2022. In questa schermata abbiamo selezionato ".NET 6.0 (supporto a lungo termine)" dal menu a discesa Framework. Abbiamo lasciato "Abilita Docker" deselezionato.

Definizione del nostro servizio di prodotto

Ora che abbiamo creato il progetto, VS mostra un servizio di definizione prototipo gRPC di esempio denominato Greeter . Riutilizzeremo i file principali di Greeter per soddisfare le nostre esigenze.

  • Per creare il nostro contratto, sostituiremo il contenuto di greet.proto con Snippet 1, rinominando il file product.proto .
  • Per creare il nostro servizio, sostituiremo il contenuto del file GreeterService.cs con Snippet 2, rinominando il file ProductCatalogService.cs .
 using Grpc.Core; using Product; namespace InventoryAPI.Services { public class ProductCatalogService : ProductCatalog.ProductCatalogBase { public override Task<ProductDetailsReply> GetProductDetails( ProductDetailsRequest request, ServerCallContext context) { return Task.FromResult(new ProductDetailsReply { Id = request.Id, Name = "Purple Bowtie", Sku = "purbow", Price = new Price { Amount = 100, CurrencyCode = "USD" } }); } } }
Snippet 2: ProductCatalogService

Il servizio ora restituisce un prodotto hardcoded. Per far funzionare il servizio, è sufficiente modificare la registrazione del servizio in Program.cs per fare riferimento al nuovo nome del servizio. Nel nostro caso, rinomineremo app.MapGrpcService<GreeterService>(); ad app.MapGrpcService<ProductCatalogService>(); per rendere eseguibile la nostra nuova API.

Avviso corretto: non il test del protocollo standard

Anche se potremmo essere tentati di provarlo, non possiamo testare il nostro servizio gRPC tramite un browser puntato al suo endpoint. Se dovessimo tentare questa operazione, riceveremmo un messaggio di errore che indica che la comunicazione con gli endpoint gRPC deve essere effettuata tramite un client gRPC.

Creazione del cliente

Per testare il nostro servizio, utilizziamo il modello di base dell'app Console di VS e creiamo un client gRPC per chiamare l'API. Ho chiamato la mia InventoryApp .

Per convenienza, facciamo riferimento a un relativo percorso di file attraverso il quale condivideremo il nostro contratto. Aggiungeremo manualmente il riferimento al file .csproj . Quindi, aggiorneremo il percorso e imposteremo la modalità Client . Nota: ti consiglio di acquisire familiarità e di avere fiducia nella struttura della tua cartella locale prima di utilizzare il riferimento relativo.

Di seguito sono riportati i riferimenti .proto , come appaiono sia nel servizio che nei file di progetto del client:

File di progetto di servizio
(Codice da copiare nel file di progetto del cliente)
File di progetto del cliente
(Dopo aver incollato e modificato)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

Ora, per chiamare il nostro servizio, sostituiremo i contenuti di Program.cs . Il nostro codice raggiungerà una serie di obiettivi:

  1. Crea un canale che rappresenti la posizione dell'endpoint del servizio (la porta può variare, quindi consulta il file launchsettings.json per il valore effettivo).
  2. Crea l'oggetto client.
  3. Costruisci una semplice richiesta.
  4. Invia la richiesta.
 using System.Text.Json; using Grpc.Net.Client; using Product; var channel = GrpcChannel.ForAddress("https://localhost:7200"); var client = new ProductCatalog.ProductCatalogClient(channel); var request = new ProductDetailsRequest { Id = 1 }; var response = await client.GetProductDetailsAsync(request); Console.WriteLine(JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true })); Console.ReadKey();
Snippet 3: Nuovo Program.cs

Preparazione per il lancio

Per testare il nostro codice, in VS, faremo clic con il pulsante destro del mouse sulla soluzione e sceglieremo Set Startup Projects . Nella finestra di dialogo Pagine delle proprietà della soluzione:

  • Seleziona il pulsante di opzione accanto a Più progetti di avvio e, nel menu a discesa Azione, imposta entrambi i progetti ( InventoryAPI e InventoryApp ) su Start .
  • Fare clic su OK .

Ora possiamo avviare la soluzione facendo clic su Avvia nella barra degli strumenti VS (o premendo il tasto F5 ). Verranno visualizzate due nuove finestre della console: una per dirci che il servizio è in ascolto, l'altra per mostrarci i dettagli del prodotto recuperato.

Condivisione del contratto gRPC

Ora utilizziamo un altro metodo per connettere il client gRPC alla definizione del nostro servizio. La soluzione di condivisione del contratto più accessibile al cliente consiste nel rendere disponibili le nostre definizioni tramite un URL. Altre opzioni sono molto fragili (file condiviso tramite un percorso) o richiedono uno sforzo maggiore (contratto condiviso tramite un pacchetto nativo). La condivisione tramite un URL (come fanno SOAP e Swagger/OpenAPI) è flessibile e richiede meno codice.

Per iniziare, rendi disponibile il file .proto come contenuto statico. Aggiorneremo il nostro codice manualmente perché l'interfaccia utente nell'azione di compilazione è impostata su "Protobuf Compiler". Questa modifica indica al compilatore di copiare il file .proto in modo che possa essere servito da un indirizzo Web. Se questa impostazione venisse modificata tramite l'interfaccia utente di VS, la build verrebbe interrotta. Il nostro primo passo, quindi, è aggiungere Snippet 4 al file InventoryAPI.csproj :

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
Snippet 4: codice da aggiungere al file di progetto del servizio InventoryAPI

Successivamente, inseriamo il codice nello Snippet 5 nella parte superiore del file ProductCatalogService.cs per configurare un endpoint per restituire il nostro file .proto :

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
Snippet 5: Importazioni dello spazio dei Namespace

E ora aggiungiamo Snippet 6 appena prima app.Run() , anche nel file ProductCatalogService.cs :

 var provider = new FileExtensionContentTypeProvider(); provider.Mappings.Clear(); provider.Mappings[".proto"] = MediaTypeNames.Text.Plain; app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(app.Environment.ContentRootPath, "Protos")), RequestPath = "/proto", ContentTypeProvider = provider }); app.UseRouting();
Snippet 6: codice per rendere accessibili i file .proto tramite l'API

Con gli Snippet 4-6 aggiunti, il contenuto del file .proto dovrebbe essere visibile nel browser.

Un nuovo cliente di prova

Ora vogliamo creare un nuovo client console che collegheremo al nostro server esistente con la procedura guidata delle dipendenze di VS. Il problema è che questa procedura guidata non parla HTTP/2. Pertanto, dobbiamo regolare il nostro server per parlare su HTTP/1 e avviare il server. Con il nostro server che ora rende disponibile il suo file .proto , possiamo creare un nuovo client di prova che si collega al nostro server tramite la procedura guidata gRPC.

  1. Per cambiare il nostro server in modo che parli su HTTP/1, modificheremo il nostro file JSON appsettings.json :
    1. Regola il campo Protocol (che si trova nel percorso Kestrel.EndpointDefaults.Protocols ) per leggere Https .
    2. Salva il file.
  2. Affinché il nostro nuovo client possa leggere queste informazioni proto , il server deve essere in esecuzione. Inizialmente, abbiamo avviato sia il client precedente che il nostro server dalla finestra di dialogo Imposta progetti di avvio di VS. Regola la soluzione server per avviare solo il progetto server, quindi avvia la soluzione. (Ora che abbiamo modificato la versione HTTP, il nostro vecchio client non può più comunicare con il server.)
  3. Quindi, crea il nuovo client di prova. Avvia un'altra istanza di VS. Ripeteremo i passaggi descritti in dettaglio nella sezione Creazione del progetto API , ma questa volta sceglieremo il modello dell'app Console . Chiameremo il nostro progetto e la nostra soluzione InventoryAppConnected .
  4. Con lo chassis del client creato, ci collegheremo al nostro server gRPC. Espandi il nuovo progetto in VS Solution Explorer.
    1. Fare clic con il pulsante destro del mouse su Dipendenze e, nel menu di scelta rapida, selezionare Gestisci servizi connessi .
    2. Nella scheda Servizi connessi, fai clic su Aggiungi un riferimento al servizio e scegli gRPC .
    3. Nella finestra di dialogo Aggiungi riferimento al servizio, scegli l'opzione URL e inserisci la versione http dell'indirizzo del servizio (ricorda di prendere il numero di porta generato casualmente da launchsettings.json ) .
    4. Fare clic su Fine per aggiungere un riferimento al servizio che può essere gestito facilmente.

Sentiti libero di confrontare il tuo lavoro con il codice di esempio per questo esempio. Poiché, sotto il cofano, VS ha generato lo stesso client che abbiamo utilizzato nel nostro primo ciclo di test, possiamo riutilizzare il contenuto del file Program.cs dal servizio precedente alla lettera.

Quando modifichiamo un contratto, dobbiamo modificare la nostra definizione di client gRPC in modo che corrisponda alla definizione .proto aggiornata. Per fare ciò, dobbiamo solo accedere ai servizi connessi di VS e aggiornare la voce del servizio pertinente. Ora il nostro progetto gRPC è completo ed è facile mantenere sincronizzati il ​​nostro servizio e il nostro client.

Il tuo prossimo candidato al progetto: gRPC

La nostra implementazione di gRPC offre uno sguardo in prima persona sui vantaggi dell'utilizzo di gRPC. REST e gRPC hanno ciascuno i propri casi d'uso ideali a seconda del tipo di contratto. Tuttavia, quando entrambe le opzioni si adattano, ti incoraggio a provare gRPC: ti metterà in testa alla curva nel futuro delle API.