1. 程式人生 > 程式設計 >grpc-node在bff中的實踐(上)

grpc-node在bff中的實踐(上)

前言

開始閱讀之前,先了解一下概念。rpc,gprc,protocol buffer,bff

什麼是grpc
  • gRPC 是一個高效能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計,提供多語言支援。gRPC使用protocol buffers作為其介面定義語言(IDL)和其基礎訊息交換格式
  • RPC(Remote Procedure Call)遠端過程呼叫。簡單來說,就是我在本地呼叫了一個函式,或者物件的方法,實際上是呼叫了遠端機器上的函式,或者遠端物件的方法,但是這個通訊過程對於程式設計師來說是透明的。

grpc呼叫過程如圖1:

grpc-call

什麼是bff

簡單來時就是為前端不同的裝置建立對應的後端。如圖:

Using one server-side BFF per user interface

hello grpc

解決概念問題後,先來看看nodejs如何實現grpc server和client呼叫。如圖1

定義 protobuf

hello.proto

syntax = "proto3"; // 語法proto3

package greeter; // 包名

/**
package greeter 包含兩個service:Hello和 SelfIntro
message 定義了rpc方法引數和返回值的結構
*/

service Hello {
    rpc SayHello (SayHelloRequest) returns (SayHelloResponse) {}
}

service SelfIntro {
    rpc IntroMyself (SelfIntroRequest) returns (SelfIntroResponse) {}
}


message SayHelloRequest {
    string name = 1;
}

message SayHelloResponse {
    string message = 1;
}


message SelfIntroRequest {
    
}

message SelfIntroResponse {
    string job = 1;
}
複製程式碼

grpc使用protobuf有兩種方式。 一種是使用Protobuf.js在執行時動態生成程式碼,另一種是使用protoc編譯器生成靜態的程式碼(生成對應的結構和方法)。 本篇例項採用前者。

載入protobuf
const grpc = require("grpc")
const protoLoader = require("@grpc/proto-loader")
const packageDescripter = protoLoader.loadSync(
    __dirname+ '/../hello.proto',{
        keepCase: true
    }
)

const
gretterPackage = grpc.loadPackageDefinition(packageDescripter).greeter 複製程式碼
server實現
// ... load proto

/**
 * 實現rpc方法 SayHello,IntroMyself
 * 在50051埠啟動服務
 */

function SayHello(call,callback) {
    callback(null,{message: 'Hello ' + call.request.name})
}

function IntroMyself(call,{job: 'program enginner'})
}

function main() {
    const server = new grpc.Server()
    server.addService(gretterPackage.Hello.service,{
        SayHello,})
    server.addService(gretterPackage.SelfIntro.service,{
        IntroMyself,})

    server.bind('0.0.0.0:50051',grpc.ServerCredentials.createInsecure())
    server.start(() => {
        console.log('server runing on prot 50051')
    })
}
複製程式碼
客戶端(stub)呼叫
// ... load proto

/**
 * 建立服務端存根(stub)
 * 呼叫遠端方法
 */
function main () {
    // 指定遠端為localhost:50051

    const stubHello = new gretterPackage.Hello('localhost:50051',grpc.credentials.createInsecure())
    const name = "joe"

    stubHello.SayHello({name},(err,response) => {
        if (err) { return console.error(err) }
        console.log('Greeting: ',response.message)
    })


    const stubIntro = new gretterPackage.SelfIntro('localhost:50051',grpc.credentials.createInsecure())

    stubIntro.IntroMyself({},response) => {
        if (err) { return console.error(err) }
        console.log('SelfIntro: my job is',response.job)
    })
}

複製程式碼

到此為止我們已經體驗了一個完整的grpc呼叫過程。通常定義protobuf和server實現由後端完成。node這一層建立客戶端,拿到rpc結果,以restful api的方式提供給前端

ts提效

我們通過proto知道遠端定義那些service,有哪些prc方法,以及呼叫引數和返回值型別。由於js是弱型別的語言,在實際應用中,沒有辦法校驗引數的合法性,也不能再寫程式碼的時候提供補全提示等。為此,引入ts解決以上問題。

但是,grpc只是支援nodejs。這就需要通過某種方式安卓proto檔案生成對應的ts檔案

解決那些問題
  1. 程式碼提示補全
  2. 引數檢查
quick look

types.ts

// This file is auto generated by grpc-code-gen,do not edit!
// tslint:disable
export namespace greeter {
  export interface SayHelloRequest {
    'name'?: string;
  }


  export interface SayHelloResponse {
    'message'?: string;
  }


  export interface SelfIntroRequest {
  }


  export interface SelfIntroResponse {
    'job'?: string;
  }


}

複製程式碼

greeter/Hello.ts

export interface IHello {
  $FILE_NAME: string;
  new (address: string,credentials: ChannelCredentials,options?: object): IHello;

  /** @deprecated 請使用: SayHelloV2 */
  SayHello(
    request: types.greeter.SayHelloRequest,options?: { timeout?: number; flags?: number; host?: string; }
  ): Promise<types.greeter.SayHelloResponse>;
  /** @deprecated 請使用: SayHelloV2 */
  SayHello(
    request: types.greeter.SayHelloRequest,metadata: MetadataMap,options?: { timeout?: number; flags?: number; host?: string; }
  ): Promise<types.greeter.SayHelloResponse>;
  
  SayHelloV2(option: {
    request: types.greeter.SayHelloRequest;
    metadata?: MetadataMap;
    options?: { timeout?: number; flags?: number; host?: string; };
  }): Promise<{ response:types.greeter.SayHelloResponse,metadata: Metadata }>;
}
export const hello: IHello = new greeter.Hello(`${serviceConfig.host}:${serviceConfig.port}`,credentials);
複製程式碼

clinet.ts

import {hello} from "greeter/Hello.ts"
import * as types from "types"

export sayHello = (req: types.greeter.SayHelloRequest):  Promise<types.greeter.SayHelloResponse> => {
    return hello.SayHello(req)
}

sayHello({name: 'grpc'})
複製程式碼