こんにちは、バックエンドエンジニアのまるです。
この記事では、Protocol BuffersのLinterの一つである Buf を使ったLintついて、実践例とともにご紹介します。
Protocol Buffersとは
Protocol Buffersは、Googleが開発したバイナリデータのシリアライズ形式です。データ記述形式の一種なのでJSONやXMLと比較されることもありますが、ProtocolBufferはバイナリ形式で保存されるため、JSONやXML形式などのテキストベースの形式よりもデータサイズが小さく、より高速にデータ転送ができます。
Protocol Buffersは、Googleが開発したRPCフレームワークであるgRPCに使われています。Protocol BuffersとgRPCの関係は、JSONとHTTP通信の関係によく似ています。HTTP通信がデータ記述形式としてJSONを使用するように、gRPCはProtocolBufferを使用して通信を行います。
Bufとその他のLinterの比較
我々のプロジェクトでは、初めはProtocol BufferのLinterとしてGoogle API Linterを採用していました。Protocol BuffersがGoogle製なので、LinterもGoogle製のものを使えば安心だろうと考えていたためです。
しかし、Google API Linterのルールはいささか厳しすぎたため、プロジェクトのシステム構成の見直しのタイミングでLinterの選定を見直すことになりました。再選定の際に決めた要件は以下の3つです。
- 最近まで更新がある(開発が止まっていない)
- 個人開発でない(今後も更新が続けられる可能性が高い)
- Lintのルールが厳しすぎない
検討したLinterは以下の通りです。
- prototool
- 直近の更新が2020年と古く、公式の発言を見ても今後アップデートされる可能性が低い。
- protolint
- 個人開発である点が不安。
- Buf
- prototoolの後継にあたる。更新頻度は高く、ルールも問題ない。
これらのLinterを使って軽くPoCを行い、最終的にはすべての要件を満たしていたBufを採用しました。
.protoファイルの準備
ここから、実際にBufを使ったlintの例をご紹介します。
Bufをインストールする前に、まずはLint対象の.protoファイルを用意しましょう。今回は、以下のような簡単な.protoファイル(book.proto)を使います。
書籍データを作成するCreateBookと、書籍データを取得するGetBookという2つのメソッドがあります。この2つのメソッドのResponseは共通のメッセージ型(Book)を使用しています。
syntax = "proto3";
package book.v1;
service BookService {
rpc CreateBook(CreateBookRequest) returns (Book);
rpc GetBook(GetBookRequest) returns (Book);
}
message Book {
string id = 1;
string title = 2;
string author = 3;
int32 pages = 4;
}
message CreateBookRequest {
string title = 1;
string author = 2;
int32 pages = 3;
}
message GetBookRequest {
string id = 1;
}
以下のようなディレクトリ構成にして、v1の下にbook.protoを置きましょう。
sample-project
└── pb-proto
└── book
└── v1
└── book.proto
これで準備完了です。
Bufのインストールとセットアップ
次に、Buf CLIをインストールします。
$ brew install bufbuild/buf/buf
Homebrew以外のインストール方法については、公式のインストールガイドを参照してください。
buf CLIのインストールが終わったら、Lintのためのセットアップをします。プロジェクト直下に以下のような内容のbuf.work.yaml
を作成しましょう。directories
にはpb-protoを指定します。
version: v1
directories:
- pb-proto
最後に以下のコマンドを実行します。これにより、 buf.yaml
がpb-protoディレクトリに生成されます。
cd pb-proto
buf mod init
最終的なディレクトリ構成が以下のようになっていたら成功です。
sample-project
├── buf.work.yaml
└── pb-proto
├── buf.yaml
└── book
└── v1
└── book.proto
BufをつかったLint
さっそくBufを使ってbook.protoをLintしてみましょう。
$ buf lint ./book/v1/book.proto
出力を見てみると、いくつかLintエラーが出ていることが分かります。
pb-proto/book/v1/book.proto:5:3:"book.v1.Book" is used as the request or response type for multiple RPCs.
pb-proto/book/v1/book.proto:5:46:RPC response type "Book" should be named "CreateBookResponse" or "BookServiceCreateBookResponse".
pb-proto/book/v1/book.proto:6:3:"book.v1.Book" is used as the request or response type for multiple RPCs.
pb-proto/book/v1/book.proto:6:40:RPC response type "Book" should be named "GetBookResponse" or "BookServiceGetBookResponse".
1つ目と3つ目は、「 Book
というメッセ―ジ型が複数のRPCで使われている」ために生じたエラーです。
Bufの公式のConfiguration and optionsページには、以下のような記述があります。
最新の Protobuf 開発において最も重要なルールの 1 つは、すべての RPC に対して一意のリクエストメッセージとレスポンスメッセージを持つことです。複数のRPC間でProtobufメッセージを共有すると、このProtobufメッセージのフィールドが変更されたときに複数のRPCが影響を受けることになります。(筆者翻訳)
Configuration and options
要は「メッセージ型を使いまわすな」ということを言っていますね。どのような名称に変更するべきかは2行目と4行目を参考にすればよいです。
修正後のbook.protoは以下のようになります。
syntax = "proto3";
package book.v1;
service BookService {
rpc CreateBook(CreateBookRequest) returns (CreateBookResponse);
rpc GetBook(GetBookRequest) returns (GetBookResponse);
}
message CreateBookRequest {
string title = 1;
string author = 2;
int32 pages = 3;
}
message CreateBookResponse {
string id = 1;
string title = 2;
string author = 3;
int32 pages = 4;
}
message GetBookRequest {
string book_id = 1;
}
message GetBookResponse {
string id = 1;
string title = 2;
string author = 3;
int32 pages = 4;
}
もう一度Lintをかけてみると、エラーが出なくなったことが分かると思います。
開発の都合で無効化したいルールがある場合は、buf.yamlにexcept
の段落を作って無効化したいルール名を指定するとよいです。
以下がRPC_RESPONSE_STANDARD_NAME
とRPC_REQUEST_RESPONSE_UNIQUE
を無効化したbuf.yamlの例です。
version: v1
breaking:
use:
- FILE
lint:
use:
- DEFAULT
except:
- RPC_RESPONSE_STANDARD_NAME
- RPC_REQUEST_RESPONSE_UNIQUE
こうすると、修正前のbook.protoでもLintが通ります。他のルールの名称については公式ページのRules and categoriesを参照してください。
終わりに
Bufを使ってProtocol BufferをLintする方法をご紹介しました。BufはLint機能を使うだけなら非常にシンプルで、学習コストがかからないと思います。
この記事がみなさんのお役に立てば幸いです。