こんにちは、バックエンドエンジニアのまるです。

この記事では、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_NAMERPC_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機能を使うだけなら非常にシンプルで、学習コストがかからないと思います。

この記事がみなさんのお役に立てば幸いです。

SHARE

  • facebook
  • twitter

SQRIPTER

AGEST Engineers

AGEST

記事一覧

AGESTのエンジニアが情報発信してます!
AGESTのサービスやソリューションのお問い合わせページはこちらです。

株式会社AGEST

Sqriptsはシステム開発における品質(Quality)を中心に、エンジニアが”理解しやすい”Scriptに変換して情報発信するメディアです

  • 新規登録/ログイン
  • 株式会社AGEST