サーバーサイドエンジニアの田実です!
YappliではネイティブアプリのAPIでgRPC-Gatewayを使って実装しています。 今回は、gRPC-Gatewayをv1からv2にアップグレードしたときに対応したことを紹介します!
マイグレーションガイドはこちら↓ github.com
1. Goのパッケージ名を変更
- "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
2. MarshalarOptionを変更
+ "google.golang.org/protobuf/encoding/protojson" - runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}), + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }),
UseProtoNames
はフィールド名にprotoファイルの名前をそのまま使うか(true)、lowerCamelを使うか(false)を制御するパラメータです。
v1ではruntime.JSONPbのOrigNameフィールドに該当します。
EmitUnpopulated
はゼロ値をJSONの値で返すか(true)、省略するか(false)を制御するパラメータです。
v1ではruntime.JSONPbのEmitDefaultsフィールドに該当します。
DiscardUnknown
は定義されていないフィールドが送られたときの挙動を制御するパタメータで、trueの場合は無視します。
v1では runtime. DisallowUnknownFields() に相当します。
例ではruntime.JSONPbを使っていますが、これをラップしたruntime.HTTPBodyMarshalerを使ってもOKです。
&runtime.HTTPBodyMarshaler{
Marshaler: &runtime.JSONPb{
// ...
}
}
HTTPBodyMarshalerはハンドラー側がapi.HttpBodyの型で返した場合、HttpBody.Dataがそのままレスポンスで返されます。これによってJSON以外やprotoファイルに依存しないレスポンスを返すことができます。
// Marshal marshals "v" by returning the body bytes if v is a // google.api.HttpBody message, otherwise it falls back to the default Marshaler. func (h *HTTPBodyMarshaler) Marshal(v interface{}) ([]byte, error) { if httpBody, ok := v.(*httpbody.HttpBody); ok { return httpBody.Data, nil } return h.Marshaler.Marshal(v) }
3. マッチルール変更の対応
あるURLに対してマッチするパターンが2つ以上ある場合、最後にマッチしたものが優先されます(WithLastMatchWins)。 v1ではデフォルトで最初にマッチしたものが優先されるので、もしこのパターンが存在する場合はprotoファイルを変更する必要があります。
例えば以下のようなprotoがあったときに、 /hoge/fuga
にリクエストするとv1では GetHogeFuga
が呼び出され、v2では GetHoge
でidパラメータに fuga
が入った状態で呼び出されます。
service HogeService { rpc GetHogeFuga(GetHogeFugaRequest) returns (GetHogeFugaResponse) { option (google.api.http) = { get: "/hoge/fuga" }; } rpc GetHoge(GetHogeRequest) returns (GetHogeResponse) { option (google.api.http) = { get: "/hoge/{id}" }; } }
この場合、アップグレード時にrpcの定義順を並び替えるとアップグレード前と同じ挙動になります。
4. protoファイルでSwaggerのannotationを変更
Swaggerのannotationを使っている場合は以下のように名前を変更します。
- import "protoc-gen-swagger/options/annotations.proto"; + import "protoc-gen-openapiv2/options/annotations.proto"; - option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
5. protoc実行オプションなどの修正
protocプラグインもv2にします。
- go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger + go get -u "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
protocで --swagger_out
になっているところを --openapiv2_out
に変更します。
- protoc --swagger_out=... + protoc --openapiv2_out=...
また、googleapisは v1のgrpc-gateway/third_party
内にあったのですが
v2だと https://github.com/googleapis/googleapis
リポジトリにあるので、こちらも対応が必要です。
まとめ
gRPC-Gatewayのv1からv2にアップグレードしたときに対応したことを紹介しました!
特にマッチルール変更はE2Eレベルのテストが無いと気付きづらいので注意が必要です 💦