隨著微服務的趨勢,一個系統裡是有多個服務組成,
而且每個服務之間都要互相通訊,所以現在開始有一陣風吹向了gRPC
。
gRPC
最麻吉的資料格式是Protocol Buffers
以往前後端溝通資料最大宗是Json格式,
但如果每個微服務都要用Json相互傳接值,
就要寫一大~堆的code~
然後就有些人崩潰了。
至少我遇到的後端崩潰了,
所以現在因為後端用 gRPC,
我認真花了五天的時間搞懂跟串接。
可能有人會問,
不是在各自的微服務中用gRPC跟Protocol就好,
為什麼跟前端通訊也要用?
其實呢,Protocol Buffers是一種資料格式
。
照以往流程中,API文件會定義Json格式,
然後前後端各自寫自己語言的model去對應。
但Protocol Buffers格式,
可以自動生成符合你用的程式語言的檔案,
也就是說前後端可以把Protocol Buffers自動轉成自己要的格式,
然後雙方都不用再寫model了。
(其實我還是會寫對應的model)
結論是等於是寫一次Protocol Buffers
,各種語言的model都可以自動生成。
所以網上很多文章說Protocol Buffers本身是結構定義檔。
syntax = "proto3";
package pb;
import "base.proto";
message Customer {
int64 id = 1;
string name = 2;
string account = 3;
string password = 4;
int32 status = 5;
int64 level_id = 6;
string level_name = 7;
int64 inserted = 8;
int64 insert_by = 9;
int64 updated = 10;
int64 update_by = 11;
}
現在只要看Protocol Buffers裡的物件,就知道參數跟型態有哪些了。
為什麼Protocol Buffers能自動生成各語言的model呢?
因為有出protoc
編譯器可以轉成各語言的model。
以上述例子,要把這個 proto檔 轉給js使用,
於是我們用protoc
把上面那隻service轉成js跟ts檔。
轉完的檔案會有customer_pb.d.ts
、customer_pb.js
。
所以開發時只要寫出了一份Protocol後,前後端就可以各自把這份轉成自己的語言。
protoc
使用說明:操作於 Windows10 64bit
後端 Golang
前端 Angular
建立一個資料夾放後產出的 go 檔
格式 : protoc --go_out=plugins=grpc:${要輸出的資料夾} -I ${protofile 定義的路徑} filename
實例 : protoc --go_out=plugins=grpc:../pb/golang -I C:/Protofiles *.proto
建立一個資料夾放產出的 js ts 檔
格式 : protoc --js_out=import_style=commonjs,binary:${要輸出的資料夾}
--grpc-web_out=import_style=typescript,mode=grpcweb:${要輸出的資料夾} \
-I ${protofile 定義的路徑} filename
實際 : protoc --js_out=import_style=commonjs,binary:../pb/ts
--grpc-web_out=import_style=typescript,mode=grpcweb:../pb/ts
-I C:/Protofiles *.proto
上述流程如果看不太懂,別擔心我也是!
前端碰這些東西總是很難理解,請各位前端要跟後端打好關係,
然後請後端協助。^_^
grpc是一種通訊協定。
以往我們比較常聽到的協定如:HttpRequest、WebSocket。
不管哪種協定都是用來與後端伺服器通訊。
為了讓大家好理解,以下舉個例子:
HttpClient
:這是Angular包好的HTTP協定。
//HttpRequest
export class WebService {
headers: HttpHeaders;
constructor(private http: HttpClient) {}
setHeaders(token: string) {
this.headers = new HttpHeaders().set("Authentication", token);
}
getF<T>(url: string): Observable<T[]> {
return this.http.get<T[]>(url, { headers: this.headers }).pipe(
tap(res => this.logger.print("response", res)),
catchError(this.handleError)
);
}
...
}
--
WebSocket
:以及下面例子WebSocket協定 new WebSocket();
//WebSocket
export class WebService {
...
private create(): Subject<MessageEvent> {
const ws = new WebSocket(this.url);
const observable = Observable.create(
(obs: Observer<MessageEvent>) => {
ws.onmessage = obs.next.bind(obs);
ws.onerror = obs.error.bind(obs);
ws.onclose = obs.complete.bind(obs);
return ws.close.bind(ws);
});
const observer = {
next: (data: Object) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
};
return Subject.create(observer, observable).share();
}
}
--
gRPC Web
那我們採用gRPC協定,也要事先做一個連線處理的service,
並且這個service還要能給js使用。
所以我們用proto來寫個service,之後讓它各自轉成前後端語言。
//store.proto
syntax="proto3";
package pb;
import "base.proto";
import "admin.proto";
import "customer.proto";
import "product.proto";
import "order.proto";
service Store {
...
// customer 修讀寫
rpc FindCustomer(CustomerF) returns (CustomerFResult) {}
rpc InsertCustomer(Customer) returns (DbMessage) {}
rpc UpdateCustomer(Customer) returns (DbMessage) {}
...
}
然後要把這個 proto檔 轉給js使用,
於是我們用 protoc 把上面那隻service轉成js跟ts檔。
轉完的檔案就會有store_pb.d.ts
、store_pb.js
、StoreServiceClientPb.ts
。
用
Protocol Buffers
還有個好處是不用另外編寫API文件,
像上述把寫個service.proto檔,易讀性很高,
前後端可以直接維護這份proto檔就好。
之後寫連線的部分:
export class WebService {
apiUrl = APIURL;
store: StoreClient;
constructor() {
this.store = new StoreClient(this.apiUrl, {}, {});
}