
— grpc-web 串接步驟及開發經驗分享。
前言
前陣子因為外包團隊開發的 WebSocket 極度不穩定,主管決定在內部先嘗試將服務替換為 grpc 的解決方案。身為前端工程師的我,本來也很期待這次的技術嘗試,grpc 又是 Google 所主持的 Project,開發體驗及可靠性應該很高吧!然而實際串接後,我有了不一樣的想法…。我們還是先從技術的部分開始吧!
gRPC
gRPC is a modern open source high performance RPC framework that can run in any environment.
簡單來說 gRPC 是 Google 製作的 RPC 框架,與我們一般常串接的 REST API 有些差別。本篇將不細究其中差異,直接以前端的角度分享如何建立及串接 gRPC 服務。而我們建置的步驟主要依賴官方文件,以及針對 web 端開發的 grpc-web 套件。
首先 gRPC 有個最重要的定義檔 .proto,它的功能類似於我們在使用 npm 進行套件管理的 package.json,也就是告訴 npm 該去下載什麼版本的套件;而 .proto 則是定義了你的 gRPC 會有什麼樣的功能,並透過 protocol buffer compiler (protoc)進行編譯為程式語言。
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
}上面的例子是一個呼叫訊息的 .proto ,這份檔案對前後端來說是共用的,後端要將 .proto 編譯為 Java, Golang, etc. 來提供服務;而前端當然是編譯成 Javascript ,再搭配 grpc-web 來串接囉。
不過定義檔多是靠後端提供,必竟他們才是服務的提供者,前端同學們只需要每天催他們,把 .proto 給我交出來啦!😆😆😆
grpc-web
有了上述的背景之後,我們就來看看文件的說明吧。首先第一步當然就是安裝 grpc-web 套件在你的專案之中。以下提供官方範例的 package.json。
{
"name": "grpc-web-commonjs-example",
"dependencies": {
"google-protobuf": "^3.6.1",
"grpc-web": "^0.4.0"
},
"devDependencies": {
"browserify": "^16.2.2",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0"
}
}再來 .proto 是需要編譯的,故我們需要在系統層安裝一些 plug-in 讓我們能透過 terminal 下指令進行編譯。這些套件建議按照 grpc-web 的 Readme 安裝。
(1) protoc
這是編譯 .proto 的核心工具,我們將 release 的檔案如 protoc-3.10.1-osx-x86_64.zip 下載後進行二進制安裝。不過若是 Mac 的用戶,則推薦使用 Homebrew 安裝會比較簡單。
brew install protobuf// check if installed & version
$ protoc --version
(2) protoc-gen-grpc-web
這個套件可以幫助 protoc 編譯出 javascript。這邊比較方便的方式是將專案 clone 下來透過編譯安裝。
git clone https://github.com/grpc/grpc-web.git$ cd grpc-web
$ sudo make install-plugin
準備好以上前置作業後,我們就可以在專案底下執行 protoc 指令,將 .proto 編譯出來並引用進我們的程式碼中。以下以 echo.proto 為例:
$ protoc echo.proto \
--js_out=import_style=commonjs:$OUT_DIR \
--grpc-web_out=import_style=commonjs:$OUT_DIR$OUT_DIR 是輸出檔案的位置,可自訂路徑。指令執行完成後我們會得到 echo_pb.js echo_grpc_web_pb.js 兩支程式。接下來只要在我們的 javascript 引入即可使用囉。
(3) 使用
有了編譯完成的 js 後,我們該如何使用呢?參照官方文件的範例,在需要串接 gRPC 的文件引入:
const {EchoRequest, EchoResponse} = require('./echo_pb.js');
const {EchoServiceClient} = require('./echo_grpc_web_pb.js');var echoService = new EchoServiceClient('http://localhost:8080');var request = new EchoRequest();
request.setMessage('Hello World!');echoService.echo(request, {}, function(err, response) {
// ...
});
呼叫連線的方法已經被包裝成 EchoServiceClient 這個 Class,給予連線的位址即可建立。而過去我們在 REST 架構下需要關注的 GET POST PUT DELETE 等 API 方法,在 gRPC 中也被包裝成 Fuction 來使用,而這些方法只要參照 .proto 檔就可以瞭解明確的定義。
總結
其實 gRPC 的應用通常是在處理微服務架構間的通訊,所以在後端的成熟度相對較高,以前端的角度來看會稍微有點門檻,因此下面會分享一些串接過程的負面感受 😅。
- 環境建置成本較高。有別於過去我們只需要掌握 npm 管理套件,或 webpack 的打包設定,根據以上的步驟,相信大家也發現需要一些 compiler 套件的支援,在環境及開發上更為複雜。
- gRPC 的連線於 web 的實作其實是靠 http/2 的協議達成的,雖然許多瀏覽器很早就支援,但 debug 工具卻不夠成熟(如 chrome dev tool 還沒辦法詳細檢視連線傳送的內容),甚至需要安裝第三方 proxy 軟體攔截連線內容才能詳查。
- 因為前端打出的是 http/2 的連線,必須多使用一層 Proxy 來將內容轉成後端程式看得懂的 gRPC 連線。目前官方推薦 Envoy 來做 Server side Proxy,但這可能導致服務架構更複雜。
(可參考文章建置 Enovy Proxy:https://greddywork.gitlab.io/greddyblogs/2019/11/01/envoy/) .proto的共用雖然很方便前後端同步連線方法,但若有一個改動,就必須重新編譯,在開發的流程上容易造成混亂。
基於以上的原因,個人其實還不建議基於 web 的服務使用 gRPC 來作為前後端的溝通;僅管很新很潮,傳輸穩定體積小,但若為了追求穩定又能保持一定的開發節奏,目前不成熟的環境似乎還不適合正式導入。
不過問題也是靠人解決的,若能將 .proto 的編譯導入 CI/CD ,從流程上下手,也許能夠更加優化 gRPC 的開發體驗。以後若有機會實作這一段,我會詳盡的記錄下來並分享給大家!
