gRPC 與 REST:最佳 API 協議入門

已發表: 2022-07-22

在當今的技術環境中,大多數項目都需要使用 API。 API 在服務之間架起溝通的橋樑,這些服務可能代表一個單一的、複雜的系統,但也可能駐留在不同的機器上或使用多種不兼容的網絡或語言。

許多標準技術解決了分佈式系統的服務間通信需求,例如 REST、SOAP、GraphQL 或 gRPC。 雖然 REST 是一種受歡迎的方法,但 gRPC 是一個有價值的競爭者,它提供了高性能、類型化的合約和出色的工具。

REST 概述

具象狀態轉移 (REST) 是一種檢索或操作服務數據的方法。 REST API 通常建立在 HTTP 協議之上,使用 URI 來選擇資源並使用 HTTP 動詞(例如,GET、PUT、POST)來選擇所需的操作。 請求和響應正文包含特定於操作的數據,而它們的標頭提供元數據。 為了說明,讓我們看一個通過 REST API 檢索產品的簡化示例。

在這裡,我們請求一個 ID 為11的產品資源,並指示 API 以 JSON 格式響應:

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

鑑於此請求,我們的響應(省略不相關的標頭)可能如下所示:

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

雖然 JSON 可能是人類可讀的,但在服務之間使用時並不是最佳的。 引用屬性名稱的重複性(即使是壓縮的)也會導致消息臃腫。 讓我們看看解決這個問題的替代方案。

gRPC 概述

gRPC 遠程過程調用 (gRPC) 是一種開源、基於合約的跨平台通信協議,它通過向外部客戶端公開一組功能來簡化和管理服務間通信。

gRPC 建立在 HTTP/2 之上,利用雙向流和內置傳輸層安全性 (TLS) 等功能。 gRPC 通過序列化的二進制有效負載實現更高效的通信。 它默認使用協議緩衝區作為結構化數據序列化的機制,類似於 REST 對 JSON 的使用。

然而,與 JSON 不同的是,協議緩衝區不僅僅是一種序列化格式。 它們包括其他三個主要部分:

  • .proto文件中找到的合約定義語言(我們將遵循最新的協議緩衝區語言規範 proto3。)
  • 生成的訪問函數代碼
  • 特定於語言的運行時庫

服務上可用的遠程功能(在.proto文件中定義)列在協議緩衝區文件的服務節點內。 作為開發人員,我們可以使用協議緩衝區的豐富類型系統來定義這些函數及其參數。 該系統支持各種數字和日期類型、列表、字典和可空值來定義我們的輸入和輸出消息。

這些服務定義需要對服務器和客戶端都可用。 不幸的是,除了提供對.proto文件本身的直接訪問之外,沒有共享這些定義的默認機制。

這個示例.proto文件定義了一個函數來返回一個產品條目,給定一個 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; }
片段 1: ProductCatalog服務定義

proto3 的嚴格類型和字段順序使得消息反序列化比解析 JSON 的負擔要小得多。

比較 REST 與 gRPC

回顧一下,比較 REST 與 gRPC 時最重要的一點是:

休息gRPC
跨平台是的是的
消息格式自定義但通常是 JSON 或 XML 協議緩衝區
消息負載大小中/大小的
處理複雜性更高(文本解析) 較低(定義明確的二進制結構)
瀏覽器支持是(本機) 是(通過 gRPC-Web)

在預期不太嚴格的合同和頻繁添加有效負載的情況下,JSON 和 REST 非常適合。 當合約傾向於保持靜態且速度至關重要時,gRPC 通常會勝出。 在我從事的大多數項目中,gRPC 已被證明比 REST 更輕量級和更高性能。

gRPC 服務實現

讓我們構建一個精簡的項目來探索採用 gRPC 是多麼簡單。

創建 API 項目

首先,我們將在 Visual Studio 2022 社區版 (VS) 中創建一個 .NET 6 項目。 我們將選擇ASP.NET Core gRPC 服務模板並命名項目(我們將使用InventoryAPI和其中的第一個解決方案Inventory

Visual Studio 2022 中的“配置新項目”對話框。在此屏幕中,我們在“項目名稱”字段中鍵入“InventoryAPI”,在“位置”字段中選擇“C:\MyInventoryService”,並在“解決方案名稱”字段中鍵入“Inventory” .我們未選中“將解決方案和項目放在同一目錄中”。

現在,讓我們選擇 . NET 6.0(長期支持)選項用於我們的框架:

Visual Studio 2022 中的附加信息對話框。在此屏幕中,我們從框架下拉列表中選擇了“.NET 6.0(長期支持)”。我們未選中“啟用 Docker”。

定義我們的產品服務

現在我們已經創建了項目,VS 顯示了一個名為Greeter的示例 gRPC 原型定義服務。 我們將重新調整Greeter的核心文件以滿足我們的需求。

  • 為了創建我們的合約,我們將用 Snippet 1 替換greet.proto的內容,重命名文件product.proto
  • 為了創建我們的服務,我們將用 Snippet 2 替換GreeterService.cs文件的內容,將文件重命名為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" } }); } } }
片段 2: ProductCatalogService

該服務現在返回一個硬編碼的產品。 為了使服務工作,我們只需要更改Program.cs中的服務註冊以引用新的服務名稱。 在我們的例子中,我們將重命名app.MapGrpcService<GreeterService>();app.MapGrpcService<ProductCatalogService>(); 使我們的新 API 可運行。

公平警告:不是您的標準協議測試

雖然我們可能很想嘗試它,但我們無法通過針對其端點的瀏覽器測試我們的 gRPC 服務。 如果我們嘗試這樣做,我們會收到一條錯誤消息,指示必須通過 gRPC 客戶端進行與 gRPC 端點的通信。

創建客戶端

為了測試我們的服務,讓我們使用 VS 的基本 Console App 模板並創建一個 gRPC 客戶端來調用 API。 我命名我的InventoryApp

為方便起見,讓我們引用一個相對文件路徑,我們將通過該路徑共享我們的合同。 我們將手動添加引用到.csproj文件。 然後,我們將更新路徑並設置Client模式。 注意:我建議您在使用相對引用之前熟悉並相信您的本地文件夾結構。

以下是.proto引用,它們同時出現在服務和客戶端項目文件中:

服務項目文件
(複製到客戶端項目文件的代碼)
客戶項目文件
(粘貼和編輯後)
 <ItemGroup> <Content Update="Protos\product.proto" GrpcServices="Server" /> </ItemGroup>
 <ItemGroup> <Protobuf Include="..\InventoryAPI\Protos\product.proto" GrpcServices="Client" /> </ItemGroup>

現在,為了調用我們的服務,我們將替換Program.cs的內容。 我們的代碼將實現許多目標:

  1. 創建一個表示服務端點位置的通道(端口可能會有所不同,因此請查閱launchsettings.json文件以獲取實際值)。
  2. 創建客戶端對象。
  3. 構造一個簡單的請求。
  4. 發送請求。
 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();
片段 3:新Program.cs

準備發射

要測試我們的代碼,在 VS 中,我們將右鍵單擊解決方案並選擇Set Startup Projects 。 在解決方案屬性頁對話框中,我們將:

  • 選擇Multiple startup projects旁邊的單選按鈕,然後在 Action 下拉菜單中,將兩個項目InventoryAPIInventoryApp )設置為Start
  • 單擊確定

現在我們可以通過單擊 VS 工具欄中的開始(或按F5鍵)來啟動解決方案。 將顯示兩個新的控制台窗口:一個告訴我們服務正在偵聽,另一個向我們顯示檢索到的產品的詳細信息。

gRPC 合約共享

現在讓我們使用另一種方​​法將 gRPC 客戶端連接到我們的服務定義。 客戶最容易訪問的合同共享解決方案是通過 URL 提供我們的定義。 其他選項要么非常脆弱(通過路徑共享文件),要么需要更多努力(通過本機包共享合同)。 通過 URL 共享(就像 SOAP 和 Swagger/OpenAPI 所做的那樣)是靈活的並且需要更少的代碼。

首先,將.proto文件作為靜態內容提供。 我們將手動更新我們的代碼,因為構建操作的 UI 設置為“Protobuf Compiler”。 此更改指示編譯器複製.proto文件,以便可以從 Web 地址提供它。 如果通過 VS UI 更改此設置,則構建將中斷。 那麼,我們的第一步是將 Snippet 4 添加到InventoryAPI.csproj文件中:

 <ItemGroup> <Content Update="Protos\product.proto"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Include="Protos\product.proto" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup>
片段 4:添加到InventoryAPI服務項目文件的代碼

接下來,我們將代碼插入到ProductCatalogService.cs文件頂部的代碼片段 5 中,以設置一個端點以返回我們的.proto文件:

 using System.Net.Mime; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders;
片段 5: Namespace導入

現在,我們在app.Run()之前添加 Snippet 6,也在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();
片段 6:使.proto文件可通過 API 訪問的代碼

添加 Snippets 4-6 後, .proto文件的內容應該在瀏覽器中可見。

一個新的測試客戶端

現在我們要創建一個新的控制台客戶端,我們將使用 VS 的依賴嚮導連接到我們現有的服務器。 問題是該嚮導不使用 HTTP/2。 因此,我們需要調整我們的服務器以通過 HTTP/1 進行通信並啟動服務器。 隨著我們的服務器現在可以使用它的.proto文件,我們可以構建一個新的測試客戶端,通過 gRPC 嚮導連接到我們的服務器。

  1. 要更改我們的服務器以通過 HTTP/1 進行通信,我們將編輯我們的appsettings.json JSON 文件:
    1. 調整Protocol字段(位於路徑Kestrel.EndpointDefaults.Protocols以讀取Https
    2. 保存文件。
  2. 為了讓我們的新客戶端讀取此proto信息,服務器必須正在運行。 最初,我們從 VS 的 Set Startup Projects 對話框啟動了之前的客戶端和服務器。 調整服務器解決方案以僅啟動服務器項目,然後啟動解決方案。 (現在我們已經修改了 HTTP 版本,我們的舊客戶端無法再與服務器通信。)
  3. 接下來,創建新的測試客戶端。 啟動另一個 VS 實例。 我們將重複創建 API 項目部分中詳述的步驟,但這一次,我們將選擇控制台應用程序模板。 我們將把我們的項目和解決方案命名為InventoryAppConnected
  4. 創建客戶端機箱後,我們將連接到我們的 gRPC 服務器。 在 VS 解決方案資源管理器中展開新項目。
    1. 右鍵單擊Dependencies ,然後在上下文菜單中選擇Manage Connected Services
    2. 在 Connected Services 選項卡上,單擊Add a service reference並選擇gRPC
    3. 在 Add Service Reference 對話框中,選擇URL選項並輸入服務地址的http版本(記得從launchsettings.json中獲取隨機生成的端口號)
    4. 單擊完成以添加可以輕鬆維護的服務引用。

隨意對照此示例的示例代碼檢查您的工作。 因為,在後台,VS 生成了我們在第一輪測試中使用的相同客戶端,所以我們可以重用之前服務中的Program.cs文件的內容。

當我們更改合約時,我們需要修改我們的客戶端 gRPC 定義以匹配更新的.proto定義。 為此,我們只需要訪問 VS 的 Connected Services 並刷新相關的服務條目。 現在,我們的 gRPC 項目已經完成,很容易讓我們的服務和客戶端保持同步。

您的下一個候選項目:gRPC

我們的 gRPC 實現提供了使用 gRPC 的好處的第一手資料。 REST 和 gRPC 各有自己的理想用例,具體取決於合約類型。 但是,當這兩個選項都適合時,我鼓勵您嘗試 gRPC — 它會讓您在 API 的未來處於領先地位。