gRPC vs. REST: Erste Schritte mit dem besten API-Protokoll

Veröffentlicht: 2022-07-22

In der heutigen Technologielandschaft erfordern die meisten Projekte die Verwendung von APIs. APIs überbrücken die Kommunikation zwischen Diensten, die ein einzelnes, komplexes System darstellen, sich aber auch auf separaten Computern befinden oder mehrere, inkompatible Netzwerke oder Sprachen verwenden können.

Viele Standardtechnologien adressieren die dienstübergreifenden Kommunikationsanforderungen verteilter Systeme, wie REST, SOAP, GraphQL oder gRPC. Während REST ein bevorzugter Ansatz ist, ist gRPC ein würdiger Anwärter, der hohe Leistung, getippte Verträge und hervorragende Tools bietet.

REST-Übersicht

Representational State Transfer (REST) ​​ist ein Mittel zum Abrufen oder Bearbeiten der Daten eines Dienstes. Eine REST-API ist im Allgemeinen auf dem HTTP-Protokoll aufgebaut und verwendet einen URI, um eine Ressource auszuwählen, und ein HTTP-Verb (z. B. GET, PUT, POST), um die gewünschte Operation auszuwählen. Anforderungs- und Antworttextkörper enthalten Daten, die für den Vorgang spezifisch sind, während ihre Header Metadaten bereitstellen. Sehen wir uns zur Veranschaulichung ein vereinfachtes Beispiel zum Abrufen eines Produkts über eine REST-API an.

Hier fordern wir eine Produktressource mit der ID 11 an und weisen die API an, im JSON-Format zu antworten:

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

Angesichts dieser Anfrage könnte unsere Antwort (irrelevante Header weggelassen) wie folgt aussehen:

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

Obwohl JSON für Menschen lesbar sein kann, ist es nicht optimal, wenn es zwischen Diensten verwendet wird. Die sich wiederholende Natur des Verweisens auf Eigenschaftsnamen – selbst wenn sie komprimiert sind – kann zu aufgeblähten Nachrichten führen. Sehen wir uns eine Alternative an, um dieses Problem anzugehen.

gRPC-Übersicht

gRPC Remote Procedure Call (gRPC) ist ein Open-Source-, vertragsbasiertes, plattformübergreifendes Kommunikationsprotokoll, das die Kommunikation zwischen Diensten vereinfacht und verwaltet, indem es eine Reihe von Funktionen für externe Clients verfügbar macht.

gRPC baut auf HTTP/2 auf und nutzt Funktionen wie bidirektionales Streaming und integrierte Transport Layer Security (TLS). gRPC ermöglicht eine effizientere Kommunikation durch serialisierte binäre Nutzlasten. Es verwendet standardmäßig Protokollpuffer als Mechanismus für die Serialisierung strukturierter Daten, ähnlich wie die Verwendung von JSON durch REST.

Im Gegensatz zu JSON sind Protokollpuffer jedoch mehr als ein serialisiertes Format. Sie umfassen drei weitere Hauptteile:

  • Eine Vertragsdefinitionssprache, die in .proto Dateien zu finden ist (Wir folgen proto3, der neuesten Spezifikation der Protokollpuffersprache.)
  • Generierter Accessor-Funktionscode
  • Sprachspezifische Laufzeitbibliotheken

Die Remote-Funktionen, die für einen Dienst verfügbar sind (definiert in einer .proto -Datei), sind innerhalb des Dienstknotens in der Protokollpufferdatei aufgelistet. Als Entwickler können wir diese Funktionen und ihre Parameter mithilfe des reichhaltigen Typsystems von Protokollpuffern definieren. Dieses System unterstützt verschiedene numerische und Datumstypen, Listen, Wörterbücher und Nullables, um unsere Eingabe- und Ausgabenachrichten zu definieren.

Diese Dienstdefinitionen müssen sowohl dem Server als auch dem Client zur Verfügung stehen. Leider gibt es keinen Standardmechanismus, um diese Definitionen gemeinsam zu nutzen, abgesehen vom direkten Zugriff auf die .proto -Datei selbst.

Diese .proto -Beispieldatei definiert eine Funktion zum Zurückgeben eines Produkteintrags bei gegebener 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; }
Codeausschnitt 1: Definition des ProductCatalog -Dienstes

Die strenge Typisierung und Feldreihenfolge von proto3 machen die Deserialisierung von Nachrichten erheblich weniger anstrengend als das Parsen von JSON.

Vergleich von REST und gRPC

Zusammenfassend sind die wichtigsten Punkte beim Vergleich von REST vs. gRPC:

SICH AUSRUHEN gRPC
Plattformübergreifend Ja Ja
Nachrichtenformat Benutzerdefiniert, aber im Allgemeinen JSON oder XML Protokollpuffer
Größe der Nachrichtennutzlast Mittelgroß Klein
Verarbeitungskomplexität Höher (Textanalyse) Lower (wohldefinierte binäre Struktur)
Browser-Unterstützung Ja (einheimisch) Ja (über gRPC-Web)

Wo weniger strenge Verträge und häufige Ergänzungen der Nutzlast erwartet werden, passen JSON und REST hervorragend. Wenn Verträge eher statisch bleiben und Geschwindigkeit von größter Bedeutung ist, gewinnt gRPC im Allgemeinen. In den meisten Projekten, an denen ich gearbeitet habe, hat sich gRPC als leichter und performanter als REST erwiesen.

gRPC-Dienstimplementierung

Lassen Sie uns ein optimiertes Projekt erstellen, um zu untersuchen, wie einfach es ist, gRPC einzuführen.

Erstellen des API-Projekts

Zunächst erstellen wir ein .NET 6-Projekt in Visual Studio 2022 Community Edition (VS). Wir wählen die ASP.NET Core gRPC-Dienstvorlage aus und benennen sowohl das Projekt (wir verwenden InventoryAPI ) als auch unsere erste Lösung darin ( Inventory ) .

Ein Dialogfeld „Neues Projekt konfigurieren“ in Visual Studio 2022. In diesem Bildschirm haben wir „InventoryAPI“ in das Feld „Projektname“ eingegeben, wir haben „C:\MyInventoryService“ im Feld „Speicherort“ ausgewählt und „Inventar“ in das Feld „Lösungsname“ eingegeben . Wir haben „Lösung und Projekt im selben Verzeichnis platzieren“ deaktiviert gelassen.

Wählen wir nun die . NET 6.0 (Langzeitunterstützung) Option für unser Framework:

Ein Dialogfeld mit zusätzlichen Informationen in Visual Studio 2022. In diesem Bildschirm haben wir „.NET 6.0 (langfristige Unterstützung)“ aus der Dropdownliste „Framework“ ausgewählt. Wir haben "Enable Docker" deaktiviert gelassen.

Definition unseres Produktservices

Nachdem wir das Projekt erstellt haben, zeigt VS einen beispielhaften gRPC-Prototyp-Definitionsdienst namens Greeter an. Wir werden die Kerndateien von Greeter an unsere Bedürfnisse anpassen.

  • Um unseren Vertrag zu erstellen, ersetzen wir den Inhalt von greet.proto durch Snippet 1 und benennen die Datei product.proto .
  • Um unseren Dienst zu erstellen, ersetzen wir den Inhalt der Datei GreeterService.cs durch Snippet 2 und benennen die Datei ProductCatalogService.cs um.
 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" } }); } } }
Codeausschnitt 2: ProductCatalogService

Der Dienst gibt jetzt ein hartcodiertes Produkt zurück. Damit der Dienst funktioniert, müssen wir nur die Dienstregistrierung in Program.cs ändern, um auf den neuen Dienstnamen zu verweisen. In unserem Fall benennen wir app.MapGrpcService<GreeterService>(); zu app.MapGrpcService<ProductCatalogService>(); um unsere neue API lauffähig zu machen.

Faire Warnung: Nicht Ihr Standardprotokolltest

Auch wenn wir versucht sind, es zu versuchen, können wir unseren gRPC-Dienst nicht über einen Browser testen, der auf seinen Endpunkt ausgerichtet ist. Wenn wir dies versuchen würden, würden wir eine Fehlermeldung erhalten, die darauf hinweist, dass die Kommunikation mit gRPC-Endpunkten über einen gRPC-Client erfolgen muss.

Client erstellen

Um unseren Dienst zu testen, verwenden wir die grundlegende Konsolen-App-Vorlage von VS und erstellen einen gRPC-Client zum Aufrufen der API. Ich habe meine InventoryApp genannt.

Lassen Sie uns der Einfachheit halber auf einen relativen Dateipfad verweisen, über den wir unseren Vertrag teilen. Wir fügen den Verweis manuell zur .csproj -Datei hinzu. Dann aktualisieren wir den Pfad und stellen den Client -Modus ein. Hinweis: Ich empfehle Ihnen, sich mit Ihrer lokalen Ordnerstruktur vertraut zu machen und Vertrauen in sie zu haben, bevor Sie relative Verweise verwenden.

Hier sind die .proto Referenzen, wie sie sowohl in den Dienst- als auch in den Client-Projektdateien erscheinen:

Dienstprojektdatei
(Code zum Kopieren in die Client-Projektdatei)
Client-Projektdatei
(Nach dem Einfügen und Bearbeiten)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

Um unseren Service aufzurufen, ersetzen wir jetzt den Inhalt von Program.cs . Unser Kodex wird eine Reihe von Zielen erreichen:

  1. Erstellen Sie einen Kanal, der den Standort des Dienstendpunkts darstellt (der Port kann variieren, konsultieren Sie daher die Datei launchsettings.json “ für den tatsächlichen Wert).
  2. Erstellen Sie das Client-Objekt.
  3. Erstellen Sie eine einfache Anfrage.
  4. Senden Sie die Anfrage.
 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();
Program.cs 3: Neues Programm.cs

Vorbereitung für den Start

Um unseren Code zu testen, klicken wir in VS mit der rechten Maustaste auf die Lösung und wählen Set Startup Projects . Im Dialogfeld Lösungseigenschaftenseiten werden wir:

  • Wählen Sie das Optionsfeld neben Multiple startup projects aus und setzen Sie im Dropdown-Menü Action beide Projekte ( InventoryAPI und InventoryApp ) auf Start .
  • Klicken Sie auf OK .

Jetzt können wir die Lösung starten, indem wir in der VS-Symbolleiste auf Start klicken (oder die Taste F5 drücken). Zwei neue Konsolenfenster werden angezeigt: eines, um uns mitzuteilen, dass der Dienst zuhört, das andere, um uns Details des abgerufenen Produkts anzuzeigen.

gRPC-Vertragsfreigabe

Lassen Sie uns nun eine andere Methode verwenden, um den gRPC-Client mit der Definition unseres Dienstes zu verbinden. Die für Kunden zugänglichste Lösung zur Vertragsteilung besteht darin, unsere Definitionen über eine URL verfügbar zu machen. Andere Optionen sind entweder sehr spröde (Datei wird über einen Pfad geteilt) oder erfordern mehr Aufwand (Vertrag wird über ein natives Paket geteilt). Die gemeinsame Nutzung über eine URL (wie es SOAP und Swagger/OpenAPI tun) ist flexibel und erfordert weniger Code.

Stellen Sie zunächst die .proto -Datei als statischen Inhalt zur Verfügung. Wir werden unseren Code manuell aktualisieren, da die Benutzeroberfläche der Build-Aktion auf „Protobuf Compiler“ eingestellt ist. Diese Änderung weist den Compiler an, die .proto -Datei zu kopieren, damit sie von einer Webadresse bereitgestellt werden kann. Wenn diese Einstellung über die VS-Benutzeroberfläche geändert würde, würde der Build abbrechen. Unser erster Schritt besteht also darin, Snippet 4 zur Datei InventoryAPI.csproj hinzuzufügen:

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
Codeausschnitt 4: Code zum Hinzufügen zur InventoryAPI -Dienstprojektdatei

Als Nächstes fügen wir den Code in Snippet 5 oben in der Datei ProductCatalogService.cs ein, um einen Endpunkt einzurichten, der unsere .proto -Datei zurückgibt:

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
Codeausschnitt 5: Namespace Importe

Und jetzt fügen wir Snippet 6 direkt vor app.Run() , ebenfalls in der Datei 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();
Codeausschnitt 6: Code, um .proto Dateien über die API zugänglich zu machen

Mit den hinzugefügten Snippets 4-6 sollte der Inhalt der .proto -Datei im Browser sichtbar sein.

Ein neuer Testclient

Jetzt wollen wir einen neuen Konsolenclient erstellen, den wir mit dem Abhängigkeitsassistenten von VS mit unserem vorhandenen Server verbinden. Das Problem ist, dass dieser Assistent kein HTTP/2 spricht. Daher müssen wir unseren Server so einstellen, dass er über HTTP/1 spricht, und den Server starten. Da unser Server jetzt seine .proto -Datei zur Verfügung stellt, können wir einen neuen Testclient erstellen, der sich über den gRPC-Assistenten in unseren Server einklinkt.

  1. Um unseren Server so zu ändern, dass er über HTTP/1 kommuniziert, bearbeiten wir unsere JSON-Datei appsettings.json :
    1. Passen Sie das Feld Protocol (im Pfad Kestrel.EndpointDefaults.Protocols ) so an, dass es Https liest.
    2. Speicher die Datei.
  2. Damit unser neuer Client diese proto -Informationen lesen kann, muss der Server laufen. Ursprünglich haben wir sowohl den vorherigen Client als auch unseren Server aus dem VS-Dialog Set Startup Projects gestartet. Passen Sie die Serverlösung so an, dass nur das Serverprojekt gestartet wird, und starten Sie dann die Lösung. (Nachdem wir die HTTP-Version geändert haben, kann unser alter Client nicht mehr mit dem Server kommunizieren.)
  3. Erstellen Sie als Nächstes den neuen Testclient. Starten Sie eine weitere Instanz von VS. Wir wiederholen die im Abschnitt „Erstellen des API-Projekts “ beschriebenen Schritte, aber dieses Mal wählen wir die Konsolen-App- Vorlage. Wir nennen unser Projekt und unsere Lösung InventoryAppConnected .
  4. Nachdem das Clientgehäuse erstellt wurde, stellen wir eine Verbindung zu unserem gRPC-Server her. Erweitern Sie das neue Projekt im VS Solution Explorer.
    1. Klicken Sie mit der rechten Maustaste auf Abhängigkeiten und wählen Sie im Kontextmenü Verbundene Dienste verwalten aus.
    2. Klicken Sie auf der Registerkarte Verbundene Dienste auf Dienstverweis hinzufügen und wählen Sie gRPC aus.
    3. Wählen Sie im Dialogfeld Dienstverweis hinzufügen die Option URL und geben Sie die http -Version der Dienstadresse ein (denken Sie daran, die zufällig generierte Portnummer aus launchsettings.json ) .
    4. Klicken Sie auf Fertig stellen , um eine Servicereferenz hinzuzufügen, die einfach verwaltet werden kann.

Fühlen Sie sich frei, Ihre Arbeit anhand des Beispielcodes für dieses Beispiel zu überprüfen. Da VS unter der Haube denselben Client generiert hat, den wir in unserer ersten Testrunde verwendet haben, können wir den Inhalt der Datei „ Program.cs “ aus dem vorherigen Dienst wörtlich wiederverwenden.

Wenn wir einen Vertrag ändern, müssen wir unsere Client-gRPC-Definition ändern, damit sie mit der aktualisierten .proto Definition übereinstimmt. Dazu müssen wir lediglich auf die Connected Services von VS zugreifen und den entsprechenden Serviceeintrag aktualisieren. Jetzt ist unser gRPC-Projekt abgeschlossen und es ist einfach, unseren Dienst und Client synchron zu halten.

Ihr nächster Projektkandidat: gRPC

Unsere gRPC-Implementierung bietet einen Einblick aus erster Hand in die Vorteile der Verwendung von gRPC. REST und gRPC haben je nach Vertragstyp jeweils ihre eigenen idealen Anwendungsfälle. Wenn jedoch beide Optionen passen, empfehle ich Ihnen, gRPC auszuprobieren – damit sind Sie in der Zukunft der APIs einen Schritt voraus.