1. 程式人生 > >.NET Core 3.0中用 Code-First 方式建立 gRPC 服務與客戶端

.NET Core 3.0中用 Code-First 方式建立 gRPC 服務與客戶端

.NET Core love gRPC

千呼萬喚的 .NET Core 3.0 終於在 9 月份正式釋出,在它的眾多新特性中,除了效能得到了大大提高,比較受關注的應該是 ASP.NET Core 3.0 對 gRPC 的集成了。
它的原始碼託管在 grpc-dotnet 這個 Github 庫中,由微軟 .NET 團隊與谷歌 gRPC 團隊共同維護.

.NET Core 對 gRPC 的支援在 grpc 官方倉庫早已有實現(grpc/csharp),但服務端沒有很好地與 ASP.NET Core 整合,使用起來還需要自己進行一些整合擴充套件。
而 ASP.NET Core 3.0 新增了 gRPC 服務的託管功能,能讓 gRPC 與 ASP.NET Core 框架本身的特性很好地結合,如日誌、依賴注入、身份認證和授權,並由 Kestrel 伺服器提供 HTTP/2 連結,效能上得到充分保障。

推薦把專案中已有的 RPC 框架或者內部服務間 REST 呼叫都遷移到 gRPC 上,因為它已經是雲原生應用的標準 RPC 框架,在整個 CNCF 主導下的雲原生應用開發生態裡 gRpc 有著舉足輕重的地位。

對於 gRPC 的使用方式,前段時間已經有其他大神寫的幾篇文章了,這裡就不再贅述了。
本文主要介紹的是區別於標準使用規範的,但對.NET 應用更加友好的使用方式,最後會提供原始碼來展示。

作為對比,還是要列一下標準的使用步驟:

  1. 定義 proto 檔案,包含服務、方法、訊息物件的定義
  2. 引入 Grpc.Tools Nuget 包並新增指定 proto 路徑和生成模式
  3. 生成專案,得到服務端的抽象類或客戶端的呼叫客戶端元件
  4. 實現服務端抽象類,並在 ASP.NET Core 註冊這個服務的路由端點
  5. DI 註冊 gRPC 服務。
  6. 客戶端用 Grpc.Net.ClientFactory Nuget 包進行統一配置和依賴注入

.NET Core 對 gRPC 的大力支援使開發者開發效率大大提高,入門難度也減少了許多,完全可以成為跟 WebApi 等一樣的 .NET Core 技術棧的標配。

proto 在單一語言系統架構中的侷限性

使用 proto 檔案的好處是多語言支援,同一份 proto 可以生成各種語言的服務和客戶端,可以讓用不同語言開發的微服務直接互相遠端呼叫。但 proto 檔案作為不同服務間的契約,不可以經常修改,否則就會對使用了它的服務造成不同程度的影響,因此對 proto 檔案的版本控制需要得到重視。

另外,我們的應用程式還不應該與 gRPC 耦合,否則就會導致系統架構被這些實現細節所綁架。直接依賴 proto 檔案和由它生成的程式碼,就是對 gRPC 的強耦合。

例如,當應用程式在演進的過程中,複雜度還未達到完全部署隔離的必要時,為了避免因“完全邊界”引入的部署運維複雜性,又能預留隔離的可能性,需要有一層介面層作為“不完全邊界”。

又比如,目前在 windows 系統的 iis 上還不支援 grpc-dotnet,當有 windows 上的應用程式需要使用 RPC,就需要換成 REST 的實現了。

因此,為了不讓應用程式對 gRPC 過於依賴,還應該使用一層抽象(介面)層與其解耦,用介面來隔離對 RPC 實現的依賴,這樣在需要使用不同的實現時,可以通過註冊不同的實現來方便地切換。

在這些場景下,本文要介紹的 Code-First gRPC 使用方法就發揮作用了。

Code-First gRPC

說了這麼久,我好像還沒正式介紹 Code-First gRPC,到底他有多適合在單一語言系統架構中實現 gRPC 呢?下面要介紹的就是基於大名鼎鼎的 protobuf-net 實現的 gRPC 框架,protobuf-net.Grpc。

protobuf-net 是在過去十幾年前到現在一直在 .NET 中有名的 Protobuf 庫,想用 Protobuf 序列化時就會用到這個庫。他的特性就是可以把 C# 程式碼編寫的類能以 Protobuf 的協議進行序列化和反序列化,而不是 proto 檔案再生成這些類。而 protobuf-net.Grpc 則是一脈相承,可以把 C# 寫的介面,在服務端方便地把介面的實現類註冊成 ASP.NET Core 的 gRPC 服務,在客戶端把介面動態代理實現為呼叫客戶端,呼叫前面的這個服務端。

用法很簡單,只要宣告一個介面為您的服務契約:

[ServiceContract]
public interface IMyAmazingService {
    ValueTask<SearchResponse> SearchAsync(SearchRequest request);
    // ...
}

然後實現該介面的服務端:

public class MyServer : IMyAmazingService {
    // ...
}

或者向系統獲取客戶端:

var client = http.CreateGrpcService<IMyAmazingService>();
var results = await client.SearchAsync(request);

這相當於以下 .proto 中的服務:

service MyAmazingService {
    rpc Search (SearchRequest) returns (SearchResponse) {}
    // ...
}

protobuf-net.Grpc 同樣通過普通型別定義支援 gRPC 的四種模式,把 C# 8.0 中最新的 IAsyncEnumerable 型別識別成 proto 中的 stream,單向流、雙向流都可以實現!而且用 IAsyncEnumerable 實現可比 proto 生成的類方便很多。

例如 proto 雙向流定義:

rpc chat(stream ChatRequest) returns ( stream ChatResponse);

生成出來的方法是:

Task BathTheCat(IAsyncStreamReader<ChatRequest> requestStream, IServerStreamWriter<ChatResponse> responseStream)

protobuf-net.Grpc 只要定義一個方法:

IAsyncEnumerable<ChatResponse> SubscribeAsync(IAsyncEnumerable<ChatRequest> requestStream);

由此可見,protobuf-net.Grpc 無需在契約層引入第三方庫,充分運用了 C# 型別系統,把方法、型別對映到相容了 gRPC 的服務定義上。

上文所說的 proto 侷限也迎刃而解了,函式呼叫、gRPC、REST 都能方便切換。(REST 實現可以參考我的開源框架 shriek-fx 中的 Shriek.ServiceProxy.Http )元件。

下一篇,我將主要介紹利用 protobuf-net.Grpc 的 gRPC 雙向流模式與 Blazor 實現一個簡單的線上即時聊天室。

相關連結:

  • protobuf-net.Grpc:https://github.com/protobuf-net/protobuf-net.Grpc
  • shriek-fx:https://github.com/Shriek-Projects/shriek-fx
  • GrpcChat: https://github.com/ElderJames/GrpcChat