Node.js上で、gRPCのサーバとクライアントを動かしてみます。
また、gRPCのコード生成の際に、TypeScriptの型情報も出力し、静的な型付けも可能にします。
コード全体はこちらにあります。
TypeScriptのインストール
まずは、TypeScriptをインストールします。
yarn add -D typescript ts-node
ts-node は実行するのに楽なので入れました。普通に tsc でコンパイルしてから node で実行しても良いです。
proto ファイルを書く
proto ファイルに gRPC サービスの定義を書きます。今回は BookService
というものを用意しました。
syntax = "proto3";
service BookService {
rpc GetBook(GetBookRequest) returns (GetBookResponse);
}
message GetBookRequest {
string id = 1;
}
message GetBookResponse {
Book book = 1;
}
message Book {
string title = 1;
string author = 2;
}
proto ファイルからコード生成を行う
proto ファイルから、コード生成を行います。
生成するのは、JavaScriptファイル(.js)とTypeScriptの型定義ファイル(.d.ts)です。
ちなみに、公式のチュートリアルにあるように、Node.jsでgRPCを実装する場合には、実行時にコード生成をおこなう方法(dynamic codegen)と、
事前にprotocで事前にコード生成を行う方法(static codegen)の二種類があります。
今回は、後者の方法をとります。
Node.jsのコード生成をおこなうには、grpc-toolsというnpmパッケージを使用します。
これには、 protoc と その gRPC Node プラグインが同梱されています。
TypeScriptの型定義を生成するには、また別の protoc プラグインである、grpc_tools_node_protoc_ts をインストールします。
yarn add -D grpc-tools grpc_tools_node_protoc_ts
インストールをおこなったら、次のようなシェルスクリプトを用意します。
#!/usr/bin/env bash
set -eu
export PATH="$PATH:$(yarn bin)"
PROTO_SRC=./proto
PROTO_DEST=./src/proto
mkdir -p ${PROTO_DEST}
grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:${PROTO_DEST} \
--grpc_out=${PROTO_DEST} \
--plugin=protoc-gen-grpc=$(which grpc_tools_node_protoc_plugin) \
-I ${PROTO_SRC} \
${PROTO_SRC}/*
grpc_tools_node_protoc \
--plugin=protoc-gen-ts=$(npm bin)/protoc-gen-ts \
--ts_out=${PROTO_DEST} \
-I ${PROTO_SRC} \
${PROTO_SRC}/*
このスクリプトを実行すると、4つのファイルが生成されます。
src/proto/book_pb.d.ts
src/proto/book_grpc_pb.js
src/proto/book_grpc_pb.d.ts
src/proto/book_pb.js
これでコード生成は完了です。
gRPCサーバの実装
まずは、 grpc パッケージと google-protobuf パッケージをインストールします。
yarn add grpc google-protobuf
サーバのコードは次のようになります。
import * as grpc from 'grpc';
import * as book_grpc_pb from './proto/book_grpc_pb';
import * as book_pb from './proto/book_pb';
import { bookData } from './books'
class BookService implements book_grpc_pb.IBookServiceServer {
getBook(
call: grpc.ServerUnaryCall<book_pb.GetBookRequest>,
callback: grpc.sendUnaryData<book_pb.GetBookResponse>,
) {
const bookId = call.request.getId();
const response = new book_pb.GetBookResponse();
const book = new book_pb.Book();
book.setTitle(bookData[bookId].title);
book.setAuthor(bookData[bookId].author);
response.setBook(book);
callback(null, response);
}
}
(() => {
const server = new grpc.Server();
server.bind(
`0.0.0.0:50051`,
grpc.ServerCredentials.createInsecure(),
);
server.addService(
book_grpc_pb.BookServiceService,
new BookService(),
);
server.start();
})();
先の proto ファイルで定義した BookService
を実装したクラスを、TypeScriptで書きます。
BookService
には GetBook
というメソッドが定義されていました。
なので、インターフェース IBookServiceServer
を実装するには、 getBook
というメソッドを実装すればよいことになります。
class BookService implements book_grpc_pb.IBookServiceServer {
getBook(
call: grpc.ServerUnaryCall<book_pb.GetBookRequest>,
callback: grpc.sendUnaryData<book_pb.GetBookResponse>,
) {
...
}
}
そして、サービスの実装を、gRPCサーバに追加し、起動します。
server.addService(
book_grpc_pb.BookServiceService,
new BookService(),
);
server.start();
クライアントの実装
クライアントも実装してみます。
import * as grpc from 'grpc';
import * as book_grpc_pb from './proto/book_grpc_pb';
import * as book_pb from './proto/book_pb';
const client = new book_grpc_pb.BookServiceClient(
'127.0.0.1:50051',
grpc.credentials.createInsecure(),
);
const req = new book_pb.GetBookRequest();
req.setId('book1')
client.getBook(req, function(error, result) {
if (error) console.log('Error: ', error);
else console.log(result.toObject());
});
シンプルですね。
実行してみる
サーバを起動します。
yarn ts-node src/server.ts
クライアントからリクエストを送ります。
yarn ts-node src/client.ts
レスポンスが得られれば成功です。
{ book: { title: 'Book1', author: 'Author1' } }