Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8868

grpc_tools から生成した gRPC クライアントを promisify してみた。

$
0
0

前提条件

% node --version
v12.13.0
% npm --version
6.13.2

目的

最近、grpc_toolsgrpc_tools_node_protoc_tsを併せて Typescript の型ファイルと node.js の gRPC のクライアントを生成する機会がありました。生成される GRPC クライアントなのですが、Node.js にありがちな callback にて行う非同期処理です。そのため、GraphQL のリゾルバ等と組み合わせて使用する場合、非常に使い勝手が悪いです。

今回は生成された gRPC クライアントの関数を promisify します。

gRPC クライアントの生成

例えば、次のような protocol buffer から pb ファイルを生成することを考えます。

user.proto
syntax = "proto3";

package user;

option go_package = "v1";

service UserService {
    rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
}

message CreateUserRequest {
    int64 id = 1;
    string name = 2;
}

message CreateUserResponse {
    int64 id = 1;
    string name = 2;
}

grpc_tools_node_protocコマンドから pb ファイルを生成します。

dist='src/grpc/generated';\
grpc_tools_node_protoc \--js_out=import_style=commonjs,binary:${dist}\--ts_out=${dist}\--grpc_out=${dist}\-I ./proto
  ./proto/user.proto

生成された型ファイルの一部を抜粋します。

user_grpc_pb.d.ts
exportclassUserServiceClientextendsgrpc.ClientimplementsIUserServiceClient{constructor(address:string,credentials:grpc.ChannelCredentials,options?:object);publiccreateUser(request:user_pb.CreateUserRequest,callback:(error:grpc.ServiceError|null,response:user_pb.CreateUserResponse)=>void):grpc.ClientUnaryCall;publiccreateUser(request:user_pb.CreateUserRequest,metadata:grpc.Metadata,callback:(error:grpc.ServiceError|null,response:user_pb.CreateUserResponse)=>void):grpc.ClientUnaryCall;publiccreateUser(request:user_pb.CreateUserRequest,metadata:grpc.Metadata,options:Partial<grpc.CallOptions>,callback:(error:grpc.ServiceError|null,response:user_pb.CreateUserResponse)=>void):grpc.ClientUnaryCall;}

createUser関数の最後の引数がそえぞれ callback となっていることがわかります。

クライアントの実装

Promise を実装する。

まずは自力で Promise を実装します。

client.ts
import{UserServiceClient}from'./generated/user_grpc_pb';import{credentials}from'grpc';import{CreateUserRequest,CreateUserResponse}from'./generated/user_pb';exportconstcreateClient=(url:string)=>(request:CreateUserRequest):Promise<CreateUserResponse>=>{constclient=newUserServiceClient(url,credentials.createInsecure());returnnewPromise((resolve,reject)=>{client.createUser(request,(err,response)=>{err===null?resolve(response):reject(err);})});};

コレでも良いのですが、gRPC のエンドポイントが増えるたびに実装を行うのはなかなかツライです。

util.promisify を利用する。

次に Node.js の util.promisify を利用することを考えてみます。UserServiceClientクラスのメソッドを promisifyしているため、bind関数により thisを束縛しないとエラーが発生することに注意してください。

client.ts
import{UserServiceClient}from'./generated/user_grpc_pb';import{credentials}from'grpc';import{CreateUserRequest,CreateUserResponse}from'./generated/user_pb';import{promisify}from'util'exportconstcreateClient=(url:string)=>(request:CreateUserRequest):Promise<CreateUserResponse>=>{constclient=newUserServiceClient(url,credentials.createInsecure());returnpromisify<CreateUserRequest,CreateUserResponse>(client.createUser).bind(client)(request)};

エラーの有無に起因する分岐処理はなくなりましたが、ジェネリクスを指定する必要があります。

Bluebird.js を利用する。

Promise の実装である Bluebird.js を使用すると次のようになります。

client.ts
import{UserServiceClient}from'./generated/user_grpc_pb';import{credentials}from'grpc';import{CreateUserRequest,CreateUserResponse}from'./generated/user_pb';import{promisify}from'bluebird'exportconstcreateClient=(url:string)=>(request:CreateUserRequest):Promise<CreateUserResponse>=>{constclient=newUserServiceClient(url,credentials.createInsecure());returnpromisify(client.createUser,{context:client})(request)};

ジェネリクスがなくなり、thisの束縛も含めてシンプルになった印象です。promisifyにより生成される関数の戻り値の型が Bluebird<unknown>から Promise<CreateUserResponse>へ暗黙的にキャストが行われている点が少し気になりますが...

所感

今の所、Bluebird.js を利用した promisify がベターだと感じています。そもそものクライアントの実装をなんとかしてほしいところではあります。他の良い方法があれば是非とも教えて頂きたいです。


Viewing all articles
Browse latest Browse all 8868

Trending Articles