🕌

bufで.protoファイルからNestJSのソースコードを生成する

2024/10/26に公開

対象読者

  • gRPCを使用してプログラム間通信を行っている。
  • コマンドラインでprotocの生成コマンドを打っている。
  • 開発言語はTypescriptでNestJS & NodeJSを利用している。

今まで

プロトコルバッファの.protoファイルをprotocコマンドで開発言語のインターフェイスファイルを生成していました。

protoc \
--ts_proto_opt=esModuleInterop=true,env=node,nestJs=true,forceLong=string \
--plugin=../../node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out=src/generated src/proto

@bufbuild/buf

bufというツールがあります。
設定ファイル(buf.gen.yaml)をもとにprotoファイルから開発言語用のインターフェイスファイルを作成してくれるというものです。
一度設定ファイルを作成しておけば、いちいちコマンドラインにパラメータを入力しなくても済みます(protocでもbashファイルを作ってそれを実行されていると思いますが)。

インストール

gRPCでの通信ですので、プロジェクトにgRPCのモジュールがインストールされていることが前提です。

yarn add @grpc/grpc-js @grpc/proto-loader

Typescript言語のインターフェイスを生成するためのプラグインとして、ts-protoを使用するため、これもインストールしておきます。

yarn add -D ts-proto

Node.JSに@bufbuild/bufパッケージを開発用パッケージとしてインストールします。私はyarnを使用してますので、yarnによる記述をします。

yarn add -D @bufbuild/buf

bufの初期化

bufの初期化を行い、buf.yamlを生成します。

buf config init

buf.yamlというファイルが出来上がります。

# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE

このファイルでprotoファイルの場所やlintのルールや破壊的変更の検出方法について指定することができます。

私のプロジェクトではsrc/proto配下にprotoファイルを作成するため、buf.yamlmodules.pathにprotoファイルの場所を指定します。

# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
modules:
  - path: src/proto
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE

開発言語のインターフェイスファイルを生成するための設定ファイル(buf.gen.yaml)を作成します。

version: v1
plugins:
  - name: ts-proto
    out: src/generated
    opt: esModuleInterop=true,env=node,nestJs=true,forceLong=string
    path: ../../node_modules/.bin/protoc-gen-ts_proto

Typescript言語のインターフェイスファイルを生成するためのプラグインはts-protoを使用しているため、plugins.nameにts-protoを指定します。
出力先はsrc/generatedです。生成オプションをoptに記述します。Typescript言語のファイルを生成しますので、tsconfig.jsonの設定に合わせたほうがいいです。私はesModuleInterop=trueにしているので、optも同じにしています。
NestJsを使用しているので、nestJs=trueを追加しています。これを入れないとインターフェイスのメソッドがPascaleケースで生成されます。nestJs=trueにするとキャメルケースで出力されます。

nestJs=trueなし

export interface OrderService {
  CreateOrder(request: CreateOrderRequest): CreateOrderResponse;
}

nestJs=trueあり

export interface OrderService {
  createOrder(request: CreateOrderRequest): CreateOrderResponse;
}

forceLong=stringint64で定義したプロパティをstringで出力します。

message Order {
  int32 id = 1;
  int64 totalMoney = 2;
}

出力結果

export interface Order {
  id: number;
  totalMoney: string;
}

Typescriptのnumberint64の範囲より狭いため、numberでは受けず、一旦文字列に変換します。
受け取った文字列はbig.jsなどの数値パッケージでBig型に変換して演算を行う様にしています。

プラグインのパスはpathに記述します。モノレポで開発していますので、ルートフォルダにあるnode_modules配下にあるprotoc-gen-ts_protoを指定します。

    path: ../../node_modules/.bin/protoc-gen-ts_proto

lint

以下のコマンドでlintができます。

buf lint

ファイルの生成

開発言語のファイルを生成するには、以下のコマンドを使用します。

buf generate

これでoutに指定したフォルダーにファイルが出力されます。

まとめ

設定ファイルがあることでどの様なファイルがどこからどこへ出力されるかが一目瞭然になります。
CIでの設定もしやすくなるかもしれません。あまり変わらないかな?
コマンドラインをbashにするのであれば、人によってファイル名が変わるので、統一感はありませんが、Typescript言語での開発であれば、どのプロジェクトでも生成にはbuf generateで行えるという統一感はあります。
どの様なサービスでもdocker-compose.yamlに記述しておけばdocker compose up -dで起動でいるといった統一感がbufにはあると思うんです。言い過ぎかな?

Discussion