見出し画像

Go言語でのgRPCコード生成(2020年10月以降版)

電通デジタルでバックエンドの開発をしている齋藤です。

本記事では以前の記事(2020年5月)

でご紹介したGo言語でのgRPCコード生成の状況の続報(2020年末)をお伝えしたいと思います。

概要としては前回の記事に記載した通りで

・Protocol Buffer側のレポジトリは golang/protobuf から protocolbuffers/protobuf-go に移行
・gPRC側のレポジトリのgrpc/grpc-goに新たにprotoc-gen-go-grpcコマンドができた

なのですが、以下が追加になりました。

・gPRC用のコードを生成するには Protocol Buffersのメッセージやシリアライズに protocolbuffers/protobuf-go のprotoc-gen-go、gPRCのサーバ/クライアントに grpc/grpc-goのprotoc-gen-go-grpc、と両方を使うことになった

以降で詳細をご紹介します。

経緯

2020年3月のprotocolbuffers/protobuf-go v1.20.0のリリースでgolang/protobuf v1.3.4からブランチし

・protocolbuffers/protobuf-goではgRPCのコード生成をサポートしない
・gRPCのコードはgRPC側のgrpc/gprc-goにサポートしてもらう予定
・まだgrpc/grpc-goでのサポートが始まっていないのでgolang/protobuf v1.4.x 系が従来通りのサポートを継続する

ということになりました。

その後、2020年10月にgrpc/grpc-goでprotoc-gen-go-grpc v1.0.0をリリースしました。なお、2020年10月時点ではgolang/protobufはサポートを継続しており、同月にv1.4.3もリリースされています。

コード生成

2020年10月以前と以降で生成コマンドと生成ファイルがどう変わったかについて記載します。なお、今回利用したバージョンは

・protoc v3.12.2
・golang/protobuf v1.4.3 (2020年10月以前の方法での生成のため)
・protocolbuffers/protobuf-go v1.25.0
・grpc/grpc-go cmd/protoc-gen-go-grpc/v1.0.1

になります。また、利用するサンプルコードはgPRC公式のGo言語のQuick start

のものを利用します。

以下断りのない限り、ファイルのパスはhttps://github.com/grpc/grpc-go/tree/master/examples/helloworldからの相対パスで記載します。

以前の生成方法

golang/protobufに含まれるprotoc-gen-goを使って以下のように生成します。

$ protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative \
  helloworld/helloworld.proto

生成されるコードはhelloworld/helloworld.pb.goの1ファイルで、ファイル中に必要な定義が全て含まれています。

2020年10月以降のバージョンで可能になった生成方法

・メッセージやシリアライズはprotocolbuffers/protobuf-goのprotoc-gen-go
・gRPCのサーバ/クライアントはgrpc/grpc-goのprotoc-gen-go-grpc

を使うことになります。生成コマンドは以下に変わります。

$ protoc --go_out=. --go_opt=paths=source_relative \
   --go-grpc_out=. --go-grpc_opt=paths=source_relative \
   helloworld/helloworld.proto

生成ファイルは

・helloworld/helloworld.pb.go: メッセージやシリアライズ
・helloworld/helloworld_grpc.pb.go: gRPCのサーバ/クライアント

の2ファイルになります。

helloworld/helloworld.pb.goの方は単純に分離されただけです。helloworld/helloworld_grpc.pb.goの方は将来のための変更がありますが、既存からの移行の場合は生成時のrequire_unimplemented_serversオプションをfalseにするようにprotoc-gen-go-grpcのREADME.mdに記載があります。

つまり、移行の場合は

$ protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false,\
                                 paths=source_relative \
   helloworld/helloworld.proto

のようになります。

require_unimplemented_serversオプションをtrueにするとhelloworld/helloworld_grpc.pb.goのGreeterServerインターフェースにmustEmbedUnimplementedGreeterServer()が追加されます。該当部分のコードは以下になります(helloworld/helloworld_grpc.pb.go)。

// GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer
// for forward compatibility
type GreeterServer interface {
	// Sends a greeting
	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
	mustEmbedUnimplementedGreeterServer() // これが追加
}

このインターフェースを満たすにはGreeterServerに同ファイル中のUnimplementedGreeterServerをembeddingすることになります。該当部分のコードは以下になります(greeter_server/main.go)。

import (
    // 略
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
	pb.UnimplementedGreeterServer // embedding
}

// 中略

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{}) // ここでembedding済のserverを渡す
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

UnimplementedGreeterServerが何かと言うと、

・各gPRCに対してgPRCステータスのUnimplementedを返すメソッド
・GreeterServerのinterfaceに追加されたmustEmbedUnimplementedGreeterServer

が実装されている構造体です。(helloworld/helloworld_grpc.pb.go)

// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}

func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}

UnimplementedGreeterServerをembedすることで、未実装のRPCにはUnimplementedGreeterServerがUnimplementedを返してくれるという仕組みです。

既存でもGreeterServerのinterfaceに指定されていないだけでなので、既にembedされている方はフラグをtrueにしても問題なさそうです。

まとめ

2020年10月にgrpc/grpc-goでprotoc-gen-go-grpcがリリースされたことで、protocolbuffers/protobuf-goのprotoc-gen-goのprotoc-gen-goを使う準備ができました。生成されるコードは

・ファイルが2つに分かれる
・gRPC側のコードに変更がある
 (移行の場合はrequire_unimplemented_serversオプションをfalseにする)

という変更があるものの、現状でgolang/protobufの1.4.x系を使っている場合は移行をはじめられる状態になったと思います。

golang/protobuf 1.4.x系のサポート期限は明言されていないと思いますが、移行できる部分から移行していきたいと思います。

告知

最後に直近のイベントの告知をさせていただきます。

現在

・サイバーコミュニケーションズ様(https://www.cci.co.jp/)
・セプテーニオリジナル様(https://www.septeni-original.co.jp/)
・VOYAGE GROUP様(https://voyagegroup.com/)
・電通デジタル (https://www.dentsudigital.co.jp/)

でデジタルマーケティング領域においてそれを支える技術/エンジニアリングや組織/文化について共有するイベントを開催しています。

次回は「iOS x AD」というテーマで11/24(火)19:30より開催されますので、ご興味ある方は是非ご参加下さい!


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!