gRPC vs. REST: Pierwsze kroki z najlepszym protokołem API

Opublikowany: 2022-07-22

W dzisiejszym krajobrazie technologicznym większość projektów wymaga użycia interfejsów API. Interfejsy API łączą komunikację między usługami, które mogą reprezentować pojedynczy, złożony system, ale mogą również znajdować się na oddzielnych komputerach lub korzystać z wielu niekompatybilnych sieci lub języków.

Wiele standardowych technologii zaspokaja potrzeby komunikacji międzyusługowej systemów rozproszonych, takich jak REST, SOAP, GraphQL lub gRPC. Chociaż REST jest preferowanym podejściem, gRPC jest godnym rywalem, oferującym wysoką wydajność, typowane kontrakty i doskonałe narzędzia.

Omówienie REST

Reprezentacyjny transfer stanu (REST) ​​to sposób na pobieranie lub manipulowanie danymi usługi. Interfejs API REST jest ogólnie zbudowany na protokole HTTP, używając identyfikatora URI do wyboru zasobu i czasownika HTTP (np. GET, PUT, POST) do wybrania żądanej operacji. Treści żądania i odpowiedzi zawierają dane specyficzne dla operacji, a ich nagłówki zawierają metadane. Aby to zilustrować, spójrzmy na uproszczony przykład pobierania produktu przez REST API.

Tutaj prosimy o zasób produktu o identyfikatorze 11 i kierujemy API do odpowiedzi w formacie JSON:

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

Biorąc pod uwagę to żądanie, nasza odpowiedź (pominięto nieistotne nagłówki) może wyglądać tak:

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

Chociaż JSON może być czytelny dla człowieka, nie jest optymalny, gdy jest używany między usługami. Powtarzający się charakter odwoływania się do nazw właściwości — nawet po skompresowaniu — może prowadzić do rozdętych wiadomości. Spójrzmy na alternatywę rozwiązania tego problemu.

Przegląd gRPC

gRPC Remote Procedure Call (gRPC) to oparty na umowie, wieloplatformowy protokół komunikacyjny typu open source, który upraszcza komunikację między usługami i zarządza nią, udostępniając zestaw funkcji klientom zewnętrznym.

Zbudowany na bazie protokołu HTTP/2, gRPC wykorzystuje funkcje, takie jak dwukierunkowe przesyłanie strumieniowe i wbudowane zabezpieczenia warstwy transportowej (TLS). gRPC umożliwia bardziej wydajną komunikację za pośrednictwem serializowanych ładunków binarnych. Domyślnie używa buforów protokołu jako mechanizmu do serializacji danych strukturalnych, podobnie jak w przypadku korzystania z formatu JSON przez REST.

Jednak w przeciwieństwie do JSON bufory protokołu to coś więcej niż format serializowany. Obejmują one trzy inne główne części:

  • Język definicji kontraktu znaleziony w plikach .proto (podążamy za proto3, najnowszą specyfikacją języka bufora protokołu).
  • Wygenerowany kod funkcji akcesora
  • Biblioteki uruchomieniowe specyficzne dla języka

Funkcje zdalne dostępne w usłudze (zdefiniowane w pliku .proto ) są wymienione w węźle usługi w pliku bufora protokołu. Jako programiści możemy definiować te funkcje i ich parametry za pomocą bogatego systemu typów buforów protokołów. Ten system obsługuje różne typy numeryczne i daty, listy, słowniki i wartości null w celu zdefiniowania naszych komunikatów wejściowych i wyjściowych.

Te definicje usług muszą być dostępne zarówno dla serwera, jak i klienta. Niestety, nie ma domyślnego mechanizmu udostępniania tych definicji poza zapewnieniem bezpośredniego dostępu do samego pliku .proto .

Ten przykładowy plik .proto definiuje funkcję zwracającą wpis produktu o podanym identyfikatorze:

 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: ProductCatalog usługi katalogu produktów

Ścisłe typowanie i kolejność pól w proto3 sprawiają, że deserializacja wiadomości jest znacznie mniej obciążająca niż parsowanie JSON.

Porównanie REST z gRPC

Podsumowując, najważniejsze punkty przy porównywaniu REST z gRPC to:

RESZTA gRPC
Wieloplatformowy TAk TAk
Format wiadomości Niestandardowy, ale ogólnie JSON lub XML Bufory protokołów
Rozmiar ładunku wiadomości Średniej wielkości Mały
Złożoność przetwarzania Wyższe (parsowanie tekstu) Niższy (dobrze zdefiniowana struktura binarna)
Obsługa przeglądarki Tak (natywnie) Tak (przez gRPC-Web)

Tam, gdzie oczekuje się mniej rygorystycznych umów i częstych dodatków do ładunku, JSON i REST świetnie się sprawdzają. Gdy umowy mają tendencję do pozostawania bardziej statycznymi, a szybkość ma ogromne znaczenie, gRPC zazwyczaj wygrywa. W większości projektów, nad którymi pracowałem, gRPC okazał się lżejszy i bardziej wydajny niż REST.

Implementacja usługi gRPC

Utwórzmy uproszczony projekt, aby zbadać, jak proste jest zaadoptowanie gRPC.

Tworzenie projektu API

Na początek utworzymy projekt .NET 6 w Visual Studio 2022 Community Edition (VS). Wybierzemy szablon usługi ASP.NET Core gRPC i nazwiemy zarówno projekt (będziemy używać InventoryAPI ) , jak i nasze pierwsze rozwiązanie w nim ( Inventory ) .

Okno dialogowe „Konfiguruj nowy projekt” w programie Visual Studio 2022. Na tym ekranie wpisaliśmy „InventoryAPI” w polu Nazwa projektu, wybraliśmy „C:\MyInventoryService” w polu Lokalizacja i wpisaliśmy „Inventory” w polu Nazwa rozwiązania . Pozostawiliśmy pole „Umieść rozwiązanie i projekt w tym samym katalogu” niezaznaczone.

Teraz wybierzmy . Opcja NET 6.0 (długoterminowe wsparcie) dla naszego frameworka:

Okno dialogowe dodatkowych informacji w programie Visual Studio 2022. Na tym ekranie wybraliśmy „.NET 6.0 (wsparcie długoterminowe)” z menu rozwijanego Framework. Pozostawiliśmy pole „Włącz Docker” niezaznaczone.

Definiowanie naszej usługi produktowej

Po utworzeniu projektu program VS wyświetla przykładową usługę definicji prototypu gRPC o nazwie Greeter . Dostosujemy główne pliki Greeter do naszych potrzeb.

  • Aby utworzyć naszą umowę, greet.proto zawartość pozdrowienia.proto fragmentem 1, zmieniając nazwę pliku product.proto .
  • Aby stworzyć naszą usługę, zastąpimy zawartość pliku GreeterService.cs fragmentem 2, zmieniając nazwę pliku 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

Usługa zwraca teraz produkt zakodowany na stałe. Aby usługa działała, wystarczy zmienić rejestrację usługi w Program.cs , aby odwoływać się do nowej nazwy usługi. W naszym przypadku zmienimy nazwę app.MapGrpcService<GreeterService>(); do app.MapGrpcService<ProductCatalogService>(); aby nasz nowy interfejs API działał.

Uczciwe ostrzeżenie: nie jest to standardowy test protokołu

Chociaż możemy pokusić się o jej wypróbowanie, nie możemy przetestować naszej usługi gRPC za pomocą przeglądarki skierowanej do jej punktu końcowego. Gdybyśmy mieli spróbować tego, otrzymalibyśmy komunikat o błędzie wskazujący, że komunikacja z punktami końcowymi gRPC musi odbywać się za pośrednictwem klienta gRPC.

Tworzenie Klienta

Aby przetestować naszą usługę, użyjmy podstawowego szablonu aplikacji konsolowej VS i utwórzmy klienta gRPC do wywoływania interfejsu API. Nazwałem mój InventoryApp .

Ze względów praktycznych odwołajmy się do względnej ścieżki pliku, za pomocą której będziemy udostępniać naszą umowę. Dodamy odwołanie ręcznie do pliku .csproj . Następnie zaktualizujemy ścieżkę i ustawimy tryb Client . Uwaga: Zalecam zapoznanie się z lokalną strukturą folderów i zaufanie do niej przed użyciem odwołań względnych.

Oto odwołania .proto , które pojawiają się zarówno w plikach projektu usługi, jak i klienta:

Plik projektu usługi
(Kod do skopiowania do pliku projektu klienta)
Plik projektu klienta
(Po wklejeniu i edycji)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

Teraz, aby wywołać naszą usługę, zastąpimy zawartość Program.cs . Nasz kod zrealizuje szereg celów:

  1. Utwórz kanał, który reprezentuje lokalizację punktu końcowego usługi (port może się różnić, więc sprawdź w pliku launchsettings.json rzeczywistą wartość).
  2. Utwórz obiekt klienta.
  3. Skonstruuj proste żądanie.
  4. Wyślij prośbę.
 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: Nowy Program.cs

Przygotowanie do startu

Aby przetestować nasz kod, w programie VS kliknij prawym przyciskiem myszy rozwiązanie i wybierz opcję Ustaw projekty startowe . W oknie dialogowym Strony właściwości rozwiązania:

  • Wybierz przycisk radiowy obok wielu projektów startowych i w menu rozwijanym Akcja ustaw oba projekty ( InventoryAPI i InventoryApp ) na Start .
  • Kliknij OK .

Teraz możemy uruchomić rozwiązanie, klikając Start na pasku narzędzi VS (lub naciskając klawisz F5 ). Wyświetlą się dwa nowe okna konsoli: jedno informujące nas, że usługa nasłuchuje, drugie informujące o szczegółach pobranego produktu.

Udostępnianie umowy gRPC

Teraz użyjmy innej metody, aby połączyć klienta gRPC z definicją naszej usługi. Najbardziej dostępnym dla klienta rozwiązaniem do dzielenia się umowami jest udostępnienie naszych definicji za pośrednictwem adresu URL. Inne opcje są albo bardzo kruche (plik udostępniany przez ścieżkę) albo wymagają większego wysiłku (umowa udostępniana przez pakiet natywny). Udostępnianie za pośrednictwem adresu URL (tak jak robią to SOAP i Swagger/OpenAPI) jest elastyczne i wymaga mniej kodu.

Aby rozpocząć, udostępnij plik .proto jako zawartość statyczną. Zaktualizujemy nasz kod ręcznie, ponieważ interfejs użytkownika w akcji kompilacji jest ustawiony na „Kompilator Protobuf”. Ta zmiana nakazuje kompilatorowi skopiowanie pliku .proto , aby mógł być obsługiwany z adresu internetowego. Jeśli to ustawienie zostało zmienione za pomocą interfejsu użytkownika programu VS, kompilacja uległaby awarii. Naszym pierwszym krokiem jest zatem dodanie fragmentu 4 do pliku InventoryAPI.csproj :

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
Fragment 4: Kod do dodania do pliku projektu usługi InventoryAPI

Następnie wstawiamy kod w Snippet 5 na górze pliku ProductCatalogService.cs , aby skonfigurować punkt końcowy, który będzie zwracał nasz plik .proto :

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
Fragment 5: Importowanie Namespace

A teraz dodajemy Snippet 6 tuż ​​przed app.Run() , również w pliku 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: Kod umożliwiający dostęp do plików .proto za pośrednictwem interfejsu API

Po dodaniu fragmentów 4-6 zawartość pliku .proto powinna być widoczna w przeglądarce.

Nowy klient testowy

Teraz chcemy utworzyć nowego klienta konsoli, który połączymy się z naszym istniejącym serwerem za pomocą Kreatora zależności VS. Problem polega na tym, że ten kreator nie obsługuje protokołu HTTP/2. Dlatego musimy dostosować nasz serwer do komunikowania się przez HTTP/1 i uruchomić serwer. Gdy nasz serwer udostępnia teraz plik .proto , możemy zbudować nowego klienta testowego, który łączy się z naszym serwerem za pomocą kreatora gRPC.

  1. Aby zmienić nasz serwer na komunikację przez HTTP/1, edytujemy nasz plik JSON appsettings.json :
    1. Dostosuj pole Protocol (znajdujące się w ścieżce Kestrel.EndpointDefaults.Protocols ) , aby odczytać Https .
    2. Zapisz plik.
  2. Aby nasz nowy klient mógł proto te informacje, serwer musi być uruchomiony. Początkowo zarówno poprzedniego klienta, jak i nasz serwer uruchamialiśmy z okna dialogowego Ustaw projekty startowe programu VS. Dostosuj rozwiązanie serwerowe, aby uruchomić tylko projekt serwera, a następnie uruchom rozwiązanie. (Po zmodyfikowaniu wersji HTTP nasz stary klient nie może już komunikować się z serwerem).
  3. Następnie utwórz nowego klienta testowego. Uruchom kolejną instancję VS. Powtórzymy kroki opisane w sekcji Tworzenie projektu interfejsu API , ale tym razem wybierzemy szablon aplikacji konsolowej . Nasz projekt i rozwiązanie InventoryAppConnected .
  4. Po utworzeniu obudowy klienta połączymy się z naszym serwerem gRPC. Rozwiń nowy projekt w Eksploratorze rozwiązań VS.
    1. Kliknij prawym przyciskiem myszy Zależności i w menu kontekstowym wybierz Zarządzaj połączonymi usługami .
    2. Na karcie Połączone usługi kliknij Dodaj odwołanie do usługi i wybierz gRPC .
    3. W oknie dialogowym Add Service Reference wybierz opcję URL i wprowadź wersję http adresu usługi (pamiętaj, aby pobrać losowo wygenerowany numer portu z launchsettings.json ) .
    4. Kliknij przycisk Zakończ , aby dodać odwołanie do usługi, które można łatwo konserwować.

Zachęcamy do porównania swojej pracy z przykładowym kodem dla tego przykładu. Ponieważ pod maską VS wygenerował tego samego klienta, którego używaliśmy w naszej pierwszej rundzie testów, możemy ponownie wykorzystać zawartość pliku Program.cs z poprzedniej usługi dosłownie.

Gdy zmieniamy kontrakt, musimy zmodyfikować naszą definicję gRPC klienta, aby była zgodna ze zaktualizowaną definicją .proto . Aby to zrobić, potrzebujemy tylko dostępu do Connected Services VS i odświeżenia odpowiedniego wpisu usługi. Teraz nasz projekt gRPC jest gotowy i można łatwo synchronizować naszą usługę i klienta.

Twój następny kandydat do projektu: gRPC

Nasza implementacja gRPC zapewnia bezpośredni wgląd w korzyści płynące z używania gRPC. REST i gRPC mają swoje własne idealne przypadki użycia w zależności od typu kontraktu. Jednak gdy obie opcje pasują, zachęcam do wypróbowania gRPC — pozwoli to wyprzedzić konkurencję w przyszłości interfejsów API.