👌

Bufで構築する、現実的なスキーマファーストなREST APIの運用

2023/08/29に公開

BufというProtocol Buffersを使ったAPIを簡単に作成可能なライブラリも存在する。
Bufの言いたいだろうことをめちゃくちゃ意訳すると

  • 静的型付け言語は広く利用されていますが、API設計においてはまだスキーマ駆動のアプローチが積極的には採用されていない状況です(RESTfulやJSON、HTTPプロトコルなどが主流)。
  • しかし、APIのスキーマが自由度が高すぎるため、保守性の低下が懸念されています。初期のスタイルガイドも無視され、コードレビューに頼るケースが増えています。
  • スキーマを持つことで、クライアントとAPI間の理解が容易になり、共通の理解を得やすくなります。特に、静的型付け言語(TypeScriptやGolangなど)を用いている場合、型を定義できるため、スキーマ開発のメリットが一層増します。
  • Bufは、スキーマ駆動開発においてProtocol Buffersが最適な選択肢であると考えており、学習コストを吸収できるツールを提供しています。

buf CLIにより互換性を維持し、ベストプラクティスに準拠した一貫性のあるProtobuf APIを作成可能。
また、Linterや互換性を強制する変更検出機能も存在し、実装者間でソースコードの書き方とある程度統一可能。

$ brew install bufbuild/buf/buf

go mod initのようなノリで初期化可能。
lintでDEFAULTは厳格ですが、exceptは極力入れずに新規プロジェクトではできればこれで運用したい。

# これでbuf.yamlが生成される
$ buf mod init

$ cat buf.yaml
version: v1
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

下記のチュートリアルでイメージをふくらませる。
https://buf.build/docs/tutorials/getting-started-with-buf-cli#clone-the-git-repository

下記の公式サンプルを取ってきて、~/../buf-tour/start/getting-started-with-buf-cli/protoに移動し、buf mod initする。
現在は、googleとpetに関するprotoの定義が存在。
buf.yamlはproto配下に配置すれば良さそう。
https://github.com/bufbuild/buf-tour

.
├── buf.yaml
├── google
│   └── type
│       └── datetime.proto
└── pet
    └── v1
        └── pet.proto

buf.yamlが存在するprotoディレクトリにprotoのファイルを集めておくと良いとのこと。
コード生成するために、protoディレクトリの上の階層で生成を定義するbuf.gen.yamlを作成する。

version: v1
managed:
  enabled: true
  go_package_prefix:
    default: github.com/bufbuild/buf-tour/gen
plugins:
  - plugin: buf.build/protocolbuffers/go
    out: gen
    opt: paths=source_relative
  - plugin: buf.build/connectrpc/go
    out: gen
    opt: paths=source_relative

↑の細かい理解は後回しにして、buf.gen.yamlを置いた階層で、buf generate protoを実行すると、genに自動生成コードが吐き出される。
pet.connect.goは、pet.protoのほうにgRpcメソッドが書かれているため、同社のConnectというgRPC互換のHTTP API生成ライブラリのこと。
Connectのドキュメントも覗いたが、結局bufコマンドを使用しているので基本はほぼ同じ。
これで、protocのときのような長いコマンドを書かずとも、buf generateで簡単に生成可能。Dockerfileとdocker-compose.yamlのような関係性と似ており、MakeFileなどで定義せずともチーム内で平準化して使えそうだと思った。

.
├── buf.gen.yaml
├── gen
│   ├── google
│   │   └── type
│   │       └── datetime.pb.go
│   └── pet
│       └── v1
│           ├── pet.pb.go
│           └── petv1connect
│               └── pet.connect.go
└── proto
    ├── buf.yaml
    ├── google
    │   └── type
    │       └── datetime.proto
    └── pet
        └── v1
            └── pet.proto

チュートリアルにあるprotoファイルはいくつか修正できる部分があるので、ここはbuf lintで検出可能。

  • petIDはスネークケースにする
  • gRpcのPetStoreはPetStoreServiceにする
$ buf lint proto
proto/google/type/datetime.proto:17:1:Package name "google.type" should be suffixed with a correctly formed version, such as "google.type.v1".
proto/pet/v1/pet.proto:42:10:Field name "petID" should be lower_snake_case, such as "pet_id".
proto/pet/v1/pet.proto:47:9:Service name "PetStore" should be suffixed with "Service".

googleの方は下記の内容であり、ここの警告を無視したい場合は、buf.yamlにignoreを入れる。
https://github.com/googleapis/googleapis/blob/cd13ca4fd9ae6d280d3a7c05f69ed832c2a66315/google/type/datetime.proto

あとは↑の定義を使いサーバーを立てれば疎通できる。
https://buf.build/docs/tutorials/getting-started-with-buf-cli#implement-the-server

参考

https://tech.yappli.io/entry/migration-to-buf
https://future-architect.github.io/articles/20220623a/

Discussion