作者 | Mohamad Lawand
譯者 | 張衛濱
策劃 | 丁曉昀
從本質上來講,API 就是伺服器和用戶端之間的一個協定,指定了伺服器如何基于用戶端的請求提供特定的資料。

在建構 API 的時候,我們會想到不同的技術。根據需求不同,我們所選擇的開發 API 的技術也會随之發生變化。在目前的這個時代,主要有兩種用于建立 API 的技術:
gRPC
REST
這兩種技術都使用 HTTP 作為傳輸機制。盡管使用了相同的底層傳輸機制,但是它們的實作卻是完全不同的。
我們先對比一下這兩項技術,然後再深入了解 gRPC。
REST 是一套架構限制,而不是協定或标準。API 開發人員可以使用各種方式來實作 REST。
為了讓一個 API 被認作是 RESTful 的,我們需要遵循一些限制條件:
用戶端 - 伺服器端架構:所有的請求必須使用 HTTP 作為傳輸機制;
無狀态:API 應該是無狀态的,這意味着,伺服器不應該在伺服器端存儲任何關于用戶端會話的狀态。從用戶端到伺服器的每個請求都必須要包含所有必要的資訊以了解該請求。伺服器不能使用任何在伺服器端所存儲的上下文。
可緩存:用戶端 - 伺服器間流過的所有資料必須都是可緩存的,這意味着它們可以被存儲起來,以便于後續檢索和使用。
統一接口:用戶端和伺服器之間必須有一個接口,以便于資訊以标準的形式進行傳輸。
分層的系統:在用戶端的請求以及伺服器端的響應之間所涉及的所有伺服器必須要按照它們的職責來進行組織,組織方式不能影響到請求或響應。
gRPC 建構在 RPC(遠端過程調用,Remote Procedure Call)協定堅實的基礎之上,它也進入了 API 的領域之中。gRPC 是由谷歌開發的免費、開源的架構,它使用 HTTP/2 進行 API 通信,為 API 的設計者隐藏了 HTTP 實作。
gRPC 有很多特征,是以不管是在微服務還是在 web/ 移動 API 通信方面,都使其成為下一代 web 應用的基礎子產品:
互操作性(Interoperability):不管目前的 HTTP 版本是什麼,無論基礎設施如何變化(比如,從 HTTP 2 更新到 HTTP 3),協定必須能夠适應和改變。
分層架構:技術棧的核心面必須能夠獨立地演進和更新,而不會破壞任何使用它的應用程式。
載荷的中立性(Agnostic):不同的服務可能會需要不同的消息類型和編碼,比如 Protobuf(Protocol buffer)、JSON、XML 等。gRPC 支援所有的這些格式,并且能夠通過利用可插拔的壓縮機制來壓縮載荷。
流:gRPC 允許将大的資料集以流的方式從伺服器中轉到用戶端,反之亦然。
可插拔(Pluggable):gRPC 支援按需插入不同的功能和服務以滿足我們的需求,比如健康檢查、故障恢複和負載均衡。架構實作提供了擴充點,允許我們插入這些功能。
與 docker 和 kubernetes 類似,gRPC 是雲原生基金會(CNCF)的一部分。
簡而言之,gRPC 的好處包括:
現代、快速
開源
利用 HTTP/2
語言中立
易于添加認證、日志
為了使用 gRPC:
我們需要使用 Protobuf(Protocol Buffer)來定義消息和服務
gRPC 代碼會自動生成,我們則需要提供具體的實作。
不管是在伺服器端還是在用戶端,檔案都能支援 12 種不同的語言。
預設情況下,gRPC 會使用谷歌開源的 Protocol Buffers 機制來進行結構化資料的序列化:
它是語言中立的
能夠為任何現代程式設計語言生成代碼
資料傳輸是二進制和高效的
高度可擴充
允許我們發送大量的資料
允許我們擴充和演進 API
案例學習:
在如今的技術趨勢下,比較現代的方式是建構微服務。在本例中,我們學習一下建構航空售票系統的過程:
上圖展現了一個基于微服務的航空售票系統。在這裡,有幾個與這種類型的架構相關的關鍵點,我們需要注意:
微服務通常是由不同的語言建構的。那麼我們可以說,預訂管理服務可以基于.NET 建構,支付處理可以是基于 Java 的,而乘客資訊則是使用 Node.js 的。
每個服務都有不同的業務功能。
假設我們現在有使用不同語言編寫的微服務,它們之間要互相進行交流。當這些微服務想要交換資訊的時候,它們需要就一些事情達成共識,比如:
交換資料的 API
資料格式
錯誤格式
通路速度限制
REST 是最流行的建構 API 的方案。但是,這個決策取決于很多與我們的實作相關的架構考量:
設計資料模型的類型;
端點會是什麼樣子;
錯誤該如何進行處理;
一個用戶端可以進行多少次調用;
授權是如何實作的。
考慮到這些因素,我們再來看一下 gRPC 和 REST 的差異:
契約優先的 API 開發方式:契約(服務和消息)是在檔案中定義的,它們是 gRPC 的核心。這是以一種語言中立的方式來定義 API。這些檔案随後可以被其他程式設計語言用來生成代碼(如強類型的用戶端和消息類)。
内容是二進制的:HTTP/2 和 Protobuf 是二進制的協定,内容是為計算機和高性能而設計的。
gRPC 的設計隐藏了遠端操作的複雜性。通過使用 gRPC 庫和相關的代碼生成,我們不需要關心路由、頭資訊和序列化等問題。當需要在用戶端調用一個方法時,我們隻需要調用對應的方法就可以了。
gRPC 支援雙向的異步流:某個 gRPC 調用建立流之後,用戶端和伺服器都能在任意時間向對方發送異步流。伺服器流和用戶端流(在這種情況下,隻有響應或請求中的某一個是流)也是支援的。
gRPC 是為分布式應用的高性能和高生産率而設計的。
REST API
内容優先的 API 開發方式(URL、HTTP 方法、JSON):注重可讀性和格式化。
内容是基于文本的(HTTP 1.1 和 JSON),是以是人類可讀的。這樣造成的結果就是,它們非常适合進行調試,但是對性能并不友好。
更加強調 HTTP。我們需要考慮低級别的一些問題,這是一件好事兒,因為我們對 HTTP 請求有很大的控制權。
面向 CRUD。
最廣泛的閱聽人:每台計算機都能使用 HTTP/1.1 和 JSON,易于上手。
基于這些對比,我們可以看到這兩種方式各有其優點。但是,我們可以看到,gRPC 為基于微服務的場景提供了一組強大的特性。
使用 gRPC 建立一個伺服器 - 用戶端應用
在開始編碼之前,我們在自己的計算機上安裝以下軟體:
.NET Core 5 SDK
Visual Studio Code
軟體安裝完成之後,我們需要建立項目結構(在本文中,我們将在終端 / 指令行中直接使用dotnet指令):
我們還需要配置 SSL 信任:
接下來,我們在 VS Code 打開這個新項目,看一下都建立了哪些内容。我們可以看到,我們自動有了如下的内容:
Protos 檔案夾
Services 檔案夾
在 Protos 檔案夾中,我們有一個greet.proto檔案。正如我們在前文中所提到的,.proto能夠以語言中立的方式來定義 API。
從這個檔案中,我們可以看到,它包含一個Greeter服務和一個SayHello方法。我們可以将Greeter服務視為控制器,将SayHello方法視為一個動作。.proto檔案的内容如下所示:
SayHello方法接收一個HelloRequest(這是一個消息)并傳回一個HelloReply(這也是一個消息)。
在GreeterService檔案中,我們可以看到有一個GreeterService類,它繼承自Greeter.GreeterBase,後者是由.proto檔案自動生成的。
在SayHello方法中,我們會接收一個請求(HelloRequest)并傳回一個響應(HelloReply)。它們也是由.proto檔案自動為我們生成的。
代碼自動生成會基于.proto檔案定義為我們生成所需的檔案。gRPC 在代碼生成、路由和序列化方面為我們做了所有繁重的工作。我們所需要做的就是實作基類并覆寫方法的實作。
接下來,我們嘗試運作 gRPC 服務:
從自動生成的端點的結果中可以看到,我們不能像使用 web 浏覽器作為 REST 的用戶端那樣使用 gRPC。在這種情況下,我們需要建立一個 gRPC 用戶端與服務進行通信。對于我們的用戶端來講,gRPC 也需要.proto檔案,因為它是一個契約優先的 RPC 架構。目前,我們的 web 浏覽器對用戶端(我們并沒有.proto檔案)一無所知,是以它不知道如何處理請求。
我們建立名為customers.proto的自定義.proto檔案。這個檔案必須要在 Protos 檔案夾中建立,它的内容如下所示:
儲存完上述檔案之後,我們需要将它添加到.csproj檔案中:
現在,我們需要建構應用:
下一步是添加我們的CustomerService類到 Services 檔案夾中并更新其内容,如下所示:
現在,我們需要更新Startup.cs類,以通知我們的應用程式,我們新建立的服務有了一個新的端點。為了實作這一點,在Configure方法(位于 app.UserEndpoints 中)裡面,我們需要添加如下的代碼:
MacOS 下的注意事項:
因為 MacOS 不支援 TLS 之上的 HTTP/2,是以我們需要采用如下的方案來更新Program.cs檔案:
下一步就是建立我們的用戶端應用:
現在,我們需要添加必要的包到用戶端控制台應用中,使其能夠識别 gRPC。這可以通過在GrpcGreeterClient類中實作:
因為我們需要用戶端具有和伺服器端相同的契約,是以需要将前面步驟中建立的.proto檔案添加到用戶端應用中。為了實作這一點:
1. 首先,我們需要添加一個名為 Protos 的檔案夾到用戶端項目中。
2. 我們需要複制 gRPC greeter 服務中 Protos 檔案夾裡的内容到 gRPC 用戶端項目,即
3. 在粘貼完檔案之後,我們需要更新命名空間,使其與用戶端應用相同:
4. 我們需要更新GrpcGreeterClient.csproj檔案,以便讓它知道我們新增加的.proto檔案:
這個Protobuf元素是代碼自動生成特性了解.proto檔案的方式。通過上面的改動,我們在這裡表明,希望用戶端使用我們新添加的.proto檔案。
我們需要建構用戶端并確定所有内容都能建構成功:
現在,我們添加一些代碼到控制台應用中,以便于調用伺服器端。在Program.cs檔案中,我們需要做如下的改動:
現在,我們為應用添加流處理的功能。
我們回到customers.proto檔案并在Customer服務中添加一個流方法:
正如我們所看到的,在傳回中,我們添加了 stream 關鍵字,這意味着我們正在添加由“多個”回複所組成的stream。
同時,我們還需要添加一個空消息
要實作這個方法,我們需要到 Services 檔案夾下并添加如下的代碼到CustomerService類中:
現在,我們需要複制伺服器端 customers.proto 檔案的變化到用戶端的customers.proto檔案中:
現在,我們需要再次建構應用:
我們下一步需要更新 GrpcClientApp 中的Program.cs檔案以處理新的流方法:
現在,我們回到GrpcGreeter并更新greet.proto檔案,為其添加流方法:
可以看到,在傳回中我們添加了關鍵字stream,這意味着我們正在添加由“多個”回複所組成的stream。要實作這個方法,我們需要到 Services 檔案夾下,并在 GreeterService 中添加如下的内容:
現在,我們需要将greet.proto檔案的變更從伺服器端複制到用戶端,并對其進行建構。在用戶端應用的greet.proto檔案中,我們添加如下這行代碼:
確定在儲存.proto檔案後,對應用進行建構。
現在,我們可以打開Program.cs并使用新的方法:
該樣例闡述了我們如何在.NET 5 中實作 gRPC 的用戶端 - 伺服器應用。
總 結
我們可以看到 gRPC 在建構應用程式中的力量,但要發揮這種力量并不容易,因為建構 gRPC 服務需要更多的搭建時間以及用戶端與伺服器之間的協調。而使用 REST 的時候,我們幾乎不需要任何搭建過程就可以直接開始消費端點。
gRPC 不一定會取代 REST,因為這兩種技術都有其特定的應用場景。請基于你的業務場景和需求,為自己的項目選擇合适的技術。
作者簡介:
Mohamad Lawand是一位堅定的、具有前瞻性的技術架構師,擁有 13 年以上的工作經驗,工作範圍涉及從金融機構到政府實體等衆多行業。他積極主動,适應性強,擅長跨多平台的 SaaS 和區塊鍊技術。Mohamad 還擁有一個 Youtube 頻道,他會在那裡分享自己的知識。
https://www.infoq.com/articles/getting-started-grpc-dotnet/