gRPC vs. REST: Noțiuni introductive cu cel mai bun protocol API

Publicat: 2022-07-22

În peisajul tehnologic de astăzi, majoritatea proiectelor necesită utilizarea API-urilor. API-urile unesc comunicarea între servicii care pot reprezenta un singur sistem complex, dar pot locui și pe mașini separate sau pot folosi mai multe rețele sau limbi incompatibile.

Multe tehnologii standard se adresează nevoilor de comunicare interservicii ale sistemelor distribuite, cum ar fi REST, SOAP, GraphQL sau gRPC. În timp ce REST este o abordare favorizată, gRPC este un candidat demn, oferind performanțe înalte, contracte tipizate și instrumente excelente.

Prezentare generală REST

Transferul de stat reprezentativ (REST) ​​este un mijloc de preluare sau manipulare a datelor unui serviciu. O API REST este în general construită pe protocolul HTTP, folosind un URI pentru a selecta o resursă și un verb HTTP (de exemplu, GET, PUT, POST) pentru a selecta operația dorită. Corpurile de solicitare și de răspuns conțin date care sunt specifice operațiunii, în timp ce anteturile lor oferă metadate. Pentru a ilustra, să ne uităm la un exemplu simplificat de preluare a unui produs prin intermediul unui API REST.

Aici, solicităm o resursă de produs cu un ID de 11 și direcționăm API-ului să răspundă în format JSON:

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

Având în vedere această solicitare, răspunsul nostru (anteturi irelevante omise) poate arăta astfel:

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

Deși JSON poate fi citit de om, nu este optim atunci când este utilizat între servicii. Natura repetitivă a referințelor numelor de proprietăți – chiar și atunci când sunt comprimate – poate duce la mesaje umflate. Să ne uităm la o alternativă pentru a aborda această problemă.

Prezentare generală a gRPC

gRPC Remote Procedure Call (gRPC) este un protocol de comunicare open-source, bazat pe contract, multiplatformă, care simplifică și gestionează comunicarea interservicii prin expunerea unui set de funcții clienților externi.

Construit pe baza HTTP/2, gRPC folosește funcții precum streaming bidirecțional și Transport Layer Security (TLS) încorporat. gRPC permite o comunicare mai eficientă prin încărcături binare serializate. Folosește în mod implicit tampon de protocol ca mecanism pentru serializarea datelor structurate, similar cu utilizarea JSON de către REST.

Spre deosebire de JSON, totuși, bufferele de protocol sunt mai mult decât un format serializat. Acestea includ alte trei părți majore:

  • Un limbaj de definire a contractului găsit în fișierele .proto (Vom urma proto3, cea mai recentă specificație pentru limbajul tampon al protocolului.)
  • Codul funcției de accesorii generat
  • Biblioteci de rulare specifice limbii

Funcțiile de la distanță care sunt disponibile pe un serviciu (definite într-un fișier .proto ) sunt listate în interiorul nodului de serviciu în fișierul tampon de protocol. În calitate de dezvoltatori, ajungem să definim aceste funcții și parametrii lor folosind sistemul de tip bogat al bufferelor de protocol. Acest sistem acceptă diverse tipuri numerice și de date, liste, dicționare și numere nula pentru a defini mesajele noastre de intrare și de ieșire.

Aceste definiții de servicii trebuie să fie disponibile atât pentru server, cât și pentru client. Din păcate, nu există un mecanism implicit pentru a partaja aceste definiții în afară de furnizarea de acces direct la fișierul .proto în sine.

Acest exemplu de fișier .proto definește o funcție pentru a returna o intrare de produs, având 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; }
Fragment 1: Definiția serviciului ProductCatalog

Tastarea strictă și ordonarea câmpurilor a proto3 fac ca deserializarea mesajelor să fie considerabil mai puțin dificilă decât analizarea JSON.

Compararea REST cu gRPC

Pentru a recapitula, cele mai semnificative puncte când se compară REST cu gRPC sunt:

ODIHNĂ gRPC
Multiplatformă da da
Format mesaj Personalizat, dar în general JSON sau XML tampon de protocol
Dimensiunea încărcăturii mesajului Mediu/Mare Mic
Complexitatea procesării Mai mare (analizarea textului) Inferioară (structură binară bine definită)
Suport pentru browser Da (nativ) Da (prin gRPC-Web)

Acolo unde sunt de așteptat contracte mai puțin stricte și completări frecvente la sarcina utilă, JSON și REST se potrivesc perfect. Când contractele tind să rămână mai statice și viteza este de cea mai mare importanță, gRPC câștigă în general. În majoritatea proiectelor la care am lucrat, gRPC s-a dovedit a fi mai ușor și mai performant decât REST.

Implementarea serviciului gRPC

Să construim un proiect simplificat pentru a explora cât de simplu este să adoptați gRPC.

Crearea proiectului API

Pentru a începe, vom crea un proiect .NET 6 în Visual Studio 2022 Community Edition (VS). Vom selecta șablonul ASP.NET Core gRPC Service și vom numi atât proiectul (vom folosi InventoryAPI ) cât și prima noastră soluție din cadrul acestuia ( Inventory ) .

Un dialog „Configurați noul proiect” în Visual Studio 2022. În acest ecran am tastat „InventoryAPI” în câmpul Nume proiect, am selectat „C:\MyInventoryService” în câmpul Locație și am tastat „Inventar” în câmpul Nume soluție. . Am lăsat nebifat „Plasați soluția și proiectul în același director”.

Acum, să alegem . Opțiunea NET 6.0 (suport pe termen lung) pentru cadrul nostru:

Un dialog cu informații suplimentare în Visual Studio 2022. În acest ecran am selectat „.NET 6.0 (Suport pe termen lung)” din meniul derulant Framework. Am lăsat „Activați Docker” nebifat.

Definirea serviciului nostru de produse

Acum că am creat proiectul, VS afișează un exemplu de serviciu de definire a prototipului gRPC numit Greeter . Vom reutiliza fișierele de bază ale lui Greeter pentru a se potrivi nevoilor noastre.

  • Pentru a crea contractul nostru, vom înlocui conținutul greet.proto cu fragmentul 1, redenumind fișierul product.proto .
  • Pentru a crea serviciul nostru, vom înlocui conținutul fișierului GreeterService.cs cu Snippet 2, redenumind fișierul 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" } }); } } }
Fragment 2: ProductCatalogService

Serviciul returnează acum un produs codificat. Pentru ca serviciul să funcționeze, trebuie doar să modificăm înregistrarea serviciului în Program.cs pentru a face referire la noul nume al serviciului. În cazul nostru, vom redenumi app.MapGrpcService<GreeterService>(); la app.MapGrpcService<ProductCatalogService>(); pentru a face rulabil noul nostru API.

Avertisment corect: nu este testul dvs. standard de protocol

Deși putem fi tentați să-l încercăm, nu putem testa serviciul nostru gRPC printr-un browser care vizează punctul final al acestuia. Dacă ar fi să încercăm acest lucru, am primi un mesaj de eroare care indică faptul că comunicarea cu punctele finale gRPC trebuie făcută printr-un client gRPC.

Crearea Clientului

Pentru a testa serviciul nostru, să folosim șablonul de bază Console App al VS și să creăm un client gRPC pentru a apela API-ul. Mi-am numit InventoryApp .

Pentru comoditate, să facem referire la o cale relativă a fișierului prin care ne vom împărtăși contractul. Vom adăuga manual referința în fișierul .csproj . Apoi, vom actualiza calea și vom seta modul Client . Notă: vă recomand să vă familiarizați cu structura locală a folderelor și să aveți încredere în aceasta înainte de a utiliza referirea relativă.

Iată referințele .proto , așa cum apar atât în ​​fișierele de proiect ale serviciului, cât și ale clientului:

Fișier proiect de serviciu
(Cod de copiat în fișierul proiectului client)
Fișier proiect client
(După lipire și editare)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

Acum, pentru a apela serviciul nostru, vom înlocui conținutul Program.cs . Codul nostru va îndeplini o serie de obiective:

  1. Creați un canal care să reprezinte locația punctului final al serviciului (portul poate varia, deci consultați fișierul launchsettings.json pentru valoarea reală).
  2. Creați obiectul client.
  3. Construiți o cerere simplă.
  4. Trimite cererea.
 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();
Fragment 3: Program.cs

Pregătirea pentru lansare

Pentru a testa codul nostru, în VS, vom face clic dreapta pe soluție și vom alege Set Startup Projects . În dialogul Pagini de proprietăți ale soluției, vom:

  • Selectați butonul radio de lângă Proiecte de pornire multiple și, în meniul drop-down Acțiune, setați ambele proiecte ( InventoryAPI și InventoryApp ) la Start .
  • Faceți clic pe OK .

Acum putem porni soluția făcând clic pe Start în bara de instrumente VS (sau apăsând tasta F5 ). Se vor afișa două ferestre noi de consolă: una pentru a ne spune că serviciul ascultă, cealaltă pentru a ne arăta detaliile produsului preluat.

Partajarea contractului gRPC

Acum să folosim o altă metodă pentru a conecta clientul gRPC la definiția serviciului nostru. Cea mai accesibilă soluție de partajare a contractelor este să punem la dispoziție definițiile noastre printr-o adresă URL. Alte opțiuni sunt fie foarte fragile (fișier partajat printr-o cale), fie necesită mai mult efort (contract partajat printr-un pachet nativ). Partajarea printr-o adresă URL (cum fac SOAP și Swagger/OpenAPI) este flexibilă și necesită mai puțin cod.

Pentru a începe, faceți fișierul .proto disponibil ca conținut static. Vom actualiza codul manual, deoarece interfața de utilizare pentru acțiunea de construire este setată la „Compilatorul Protobuf”. Această modificare direcționează compilatorul să copieze fișierul .proto , astfel încât să poată fi servit de la o adresă web. Dacă această setare ar fi schimbată prin VS UI, construcția s-ar rupe. Primul nostru pas este să adăugăm fragmentul 4 la fișierul InventoryAPI.csproj :

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
Fragment 4: cod de adăugat la fișierul de proiect al serviciului InventoryAPI

Apoi, inserăm codul în fragmentul 5 din partea de sus a fișierului ProductCatalogService.cs pentru a configura un punct final pentru a returna fișierul nostru .proto :

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
Fragment 5: importuri de spații de Namespace

Și acum, adăugăm fragmentul 6 chiar înainte de app.Run() , tot în fișierul 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();
Fragment 6: Cod pentru a face fișierele .proto accesibile prin API

Cu fragmentele 4-6 adăugate, conținutul fișierului .proto ar trebui să fie vizibil în browser.

Un nou client de testare

Acum dorim să creăm un nou client de consolă pe care îl vom conecta la serverul nostru existent cu Dependency Wizard al VS. Problema este că acest expert nu vorbește HTTP/2. Prin urmare, trebuie să ne adaptăm serverul pentru a vorbi prin HTTP/1 și să pornim serverul. Cu serverul nostru care își pune acum la dispoziție fișierul .proto , putem construi un nou client de testare care se conectează la serverul nostru prin vrăjitorul gRPC.

  1. Pentru a schimba serverul nostru pentru a vorbi prin HTTP/1, vom edita fișierul JSON appsettings.json :
    1. Ajustați câmpul Protocol (găsit la calea Kestrel.EndpointDefaults.Protocols ) pentru a citi Https .
    2. Salvați fișierul.
  2. Pentru ca noul nostru client să citească aceste informații proto , serverul trebuie să ruleze. Inițial, am pornit atât clientul anterior, cât și serverul nostru din dialogul VS Set Startup Projects. Ajustați soluția de server pentru a începe doar proiectul de server, apoi porniți soluția. (Acum că am modificat versiunea HTTP, vechiul nostru client nu mai poate comunica cu serverul.)
  3. Apoi, creați noul client de testare. Lansați o altă instanță a VS. Vom repeta pașii așa cum este detaliat în secțiunea Crearea proiectului API , dar de data aceasta, vom alege șablonul Aplicație Console . Vom numi proiectul și soluția noastră InventoryAppConnected .
  4. Cu șasiul client creat, ne vom conecta la serverul nostru gRPC. Extindeți noul proiect în VS Solution Explorer.
    1. Faceți clic dreapta pe Dependențe și, în meniul contextual, selectați Gestionare servicii conectate .
    2. În fila Servicii conectate, faceți clic pe Adăugați o referință de serviciu și alegeți gRPC .
    3. În caseta de dialog Adăugați referință la serviciu, alegeți opțiunea URL și introduceți versiunea http a adresei serviciului (nu uitați să luați numărul portului generat aleatoriu din launchsettings.json ) .
    4. Faceți clic pe Terminare pentru a adăuga o referință de serviciu care poate fi întreținută cu ușurință.

Simțiți-vă liber să vă verificați munca cu exemplul de cod pentru acest exemplu. Deoarece, sub capotă, VS a generat același client pe care l-am folosit în prima noastră rundă de testare, putem reutiliza conținutul fișierului Program.cs din serviciul anterior text.

Când modificăm un contract, trebuie să modificăm definiția clientului gRPC pentru a se potrivi cu definiția .proto actualizată. Pentru a face acest lucru, trebuie doar să accesăm Serviciile conectate ale VS și să reîmprospătăm intrarea de serviciu relevantă. Acum, proiectul nostru gRPC este finalizat și este ușor să ne menținem sincronizat serviciul și clientul.

Următorul tău candidat pentru proiect: gRPC

Implementarea noastră gRPC oferă o privire directă asupra beneficiilor utilizării gRPC. REST și gRPC au fiecare propriile cazuri de utilizare ideale, în funcție de tipul de contract. Cu toate acestea, atunci când ambele opțiuni se potrivesc, vă încurajez să încercați gRPC - vă va pune înaintea curbei în viitorul API-urilor.