結構時間が経っているのですが、Go/gRPCでのサーバー開発において、2020年にコード生成の方法が変更されました。
最近gRPCを利用し始めた方は特に気にされる必要はありませんが、以前からGoでgRPCサーバーを構築していて
生成プラグインを入れ替えてないけどそろそろ更新しとくかーといった際に役立ちそうな情報として記事にしておきます。
以前の生成方法
GoでgRPCサーバーのコード生成をする際、
元はprotocプラグインとしてgolang/protobufを使用していました。
この際にはこのようなコマンドでコード生成を行なっていました。
$ protoc -I . \
--go_out=plugins=grpc:.
--go_opt=paths=source_relative \
./helloworld.proto
このコードから生成される<Service>.pb.go
に必要な全てのコードが入っていました。
protocプラグインの変更
2020年3月より、まず
protocolbuffers/protobuf-goのv1.20.0のリリースとしてGoのシリアライズコード生成プラグイン(protoc-gen-go
)が独立しました。
更に2020年10月にgrpc/grpc-goのcmd/protoc-gen-go
v1.0.0でサーバースタブ生成プラグイン(protoc-gen-go-grpc
)が独立しました。
そのため現在、.protファイルからgRPCサーバースタブを生成する際には下記のようなコマンドを打つことになります。
$ protoc -I . \
--go_out . \
--go_opt paths=source_relative \
--go-grpc_out . \
--go-grpc_opt paths=source_relative \
./helloworld.proto
以前1ファイルにまとめられていたコードも<Service>.pb.go
と<Service>_grpc.pb.go
に分かれて生成されます。
下位互換性の無い変更
また、これに伴い以前のprotoc-gen-goで生成したスタブとの間に下位互換性を崩す変更が入っています。
gRPCスタブを生成するとサービス実装用の<Service>Server
インターフェイスが生成され、
またそのインターフェイスの前方互換性の為のUnimplemented<Service>Server
構造体が生成されます。
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
type UnimplementedGreeterServer struct {
}
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
これは<Service>Server
インターフェイスの実装構造体にUnimplemented<Service>Server
をembedすることで
実際にインターフェイスのメソッドを満たさなくても実装可能とする為の仕組みですが、全てのメソッドの実装後にembedを外してしまうと、サービスを追加した際などにエラーとなってしまいます。
こうした事例が多かったからなのか、
protoc-gen-go-grpc
ではmustEmbedUnimplemented<Service>Server
のようなプライベートメソッドがインターフェイスに追加されており、
これによりUnimplemented<Service>Server
のembedが強制されるようになりました。
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
このため、Unimplemented<Service>Server
を外すような実装をしている場合、protoc-gen-go-grpc
でのコード生成に切り替えた途端にインターフェイス実装が崩れてしまうことになります。
この問題を回避する方法も用意されており、protoc-gen-go-grpcのReadmeに記載されています。
protoc-gen-go-grpc
のオプションとしてrequire_unimplemented_servers=false
を指定することで
mustEmbedUnimplemented<Service>Server
を生成しない下位互換性を維持したコード生成が可能になっています。
$ protoc -I . \
--go_out . \
--go_opt paths=source_relative \
--go-grpc_out . \
--go-grpc_opt paths=source_relative \
--go-grpc_opt require_unimplemented_servers=false \
./helloworld.proto