Chapter 04

protoファイルからコードを自動生成する

さき(H.Saki)
さき(H.Saki)
2022.06.23に更新

この章について

このままでは、protoファイルで定義したメソッドはただの机上の空論のままです。

service GreetingService {
	rpc Hello (HelloRequest) returns (HelloResponse); 
}

実際にこの通信を実現されるためには、

  • HelloRequest型のリクエストを受け取り、レスポンスをHelloRespomse型にして返すサーバー
  • HelloRequest型のリクエストを送信して、HelloRespomse型のレスポンスを受け取るクライアント

の2つが必要です。
サーバーにしてもクライアントにしても、「リクエスト・レスポンスをどうやって作るか」というビジネスロジック部分は自分で書く必要がありますが、リクエスト・レスポンスをProtocol Buffersでシリアライズ・デシリアライズするというところについては、どこでも登場する定形処理です。
その定形処理部分のコードを自動生成させることができます。

ここからは、先ほど作ったprotoファイルから、gRPC通信を実装したGoのコードを自動生成させてみましょう。

前準備

依存パッケージのインストール

まずは、コードを自動生成させるのに必要なツールをインストールしましょう。

protoファイルからコードを自動生成させるには、protocコマンドというものを使用します。
そのため、Homebrewを使ってprotocコマンドをインストールします。

$ brew install protobuf
$ which protoc
/usr/local/bin/protoc # コマンド配置箇所のパスが出力されればOK

次にGoのパッケージを2つインストールします。

$ cd src
$ go mod init mygrpc
$ go get -u google.golang.org/grpc
$ go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc

作業ディレクトリの用意

コードを配置するためのレポジトリを整備します。

まずapiディレクトリを作成し、そこにhello.protoという名前で先ほど作ったprotoファイルを配置します。
そしてpkgディレクトリ内にgrpcディレクトリを作成します。
このgrpcディレクトリ内に、protoファイルから自動生成させたコードを配置する予定です。

./src
├─ api
│   └─ hello.proto # protoファイル
├─ pkg
│   └─ grpc # ここにコードを自動生成させる
├─ go.mod
└─ go.sum

コードの生成

protocコマンドでコードを生成する

それでは実際にコードを生成させてみましょう。
apiディレクトリ直下に移動して、以下のようなprotocコマンドを叩いてみましょう。

$ cd api
$ protoc --go_out=../pkg/grpc --go_opt=paths=source_relative \
	--go-grpc_out=../pkg/grpc --go-grpc_opt=paths=source_relative \
	hello.proto

すると、pkg/grpcディレクトリ直下に、以下2つのファイルが生成されます。

  • hello.pb.go: protoファイルから自動生成されたリクエスト/レスポンス型を定義した部分のコード
  • hello_grpc.pb.go: protoファイルから自動生成されたサービス部分のコード

生成されたメッセージ型

hello.pb.goには、protoファイル内で定義したメッセージHelloRequest/HelloResponse型を、Goの構造体に定義しなおしたものが自動生成されています。

pkg/grpc/hello.pb.go
// 生成されたGoの構造体
type HelloRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

type HelloResponse struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
api/hello.proto
// protoファイルで定義したメッセージ型
message HelloRequest {
	string name = 1;
}

message HelloResponse {
	string message = 1;
}

また、それぞれの型に含まれているフィールドの値を取り出すためのゲッターも生成されています。

pkg/grpc/hello.pb.go
func (x *HelloRequest) GetName() string {
	if x != nil {
		return x.Name
	}
	return ""
}

func (x *HelloResponse) GetMessage() string {
	if x != nil {
		return x.Message
	}
	return ""
}

生成されたサーバーサイド用コード

メッセージ型以外にも、これからgRPCサーバーの中身を実装していくにあたって必要となるリソース定義も自動生成されています。

まず、protoファイル内で定義したGreetingServiceサービスが、自動生成されたhello_grpc.pb.goファイルの中にGoのGreetingServiceServerインターフェースとして定義されました。

pkg/grpc/hello_grpc.pb.go
// 生成されたGoのコード
type GreetingServiceServer interface {
	// サービスが持つメソッドの定義
	Hello(context.Context, *HelloRequest) (*HelloResponse, error)
	mustEmbedUnimplementedGreetingServiceServer()
}
api/hello.proto
// protoファイルで定義したサービス
service GreetingService {
	// サービスが持つメソッドの定義
	rpc Hello (HelloRequest) returns (HelloResponse); 
}

GreetingServiceServerインターフェースの中には、きちんとHelloRequest型を引数に、HelloResponse型を戻り値としてもつHelloメソッドも含まれていることがわかります。

そしてhello_grpc.pb.goの中に、RegisterGreetingServiceServer関数というものも生成されています。

pkg/grpc/hello_grpc.pb.go
func RegisterGreetingServiceServer(s grpc.ServiceRegistrar, srv GreetingServiceServer)

これは「第一引数で渡したgRPCサーバー上で、第二引数で渡したgRPCサービス(GreetingServiceServer)を稼働させる」ための関数です。

これら生成された

  • GreetingServiceServerインターフェース
  • RegisterGreetingServiceServer関数

をどのように使って、実際に動く自分のgRPCサーバーを実装していくかは後の章で紹介します。

生成されたクライアント用コード

gRPCにリクエストを送るためのクライアントを得るためのコンストラクタNewGreetingServiceClient関数が、hello_grpc.pb.go中に自動生成されています。

pkg/grpc/hello_grpc.pb.go
// リクエストを送るクライアントを作るコンストラクタ
func NewGreetingServiceClient(cc grpc.ClientConnInterface) GreetingServiceClient {
	return &greetingServiceClient{cc}
}

// クライアントが呼び出せるメソッド一覧をインターフェースで定義
type GreetingServiceClient interface {
	Hello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
}

このクライアントを利用して実際にgRPCサーバーにリクエストを送るところは、後ほど紹介します。

コード自動生成の仕様

protoファイルの記述がどのようなGoのコードに変換されるのかは、以下のドキュメントに詳細が記載されています。