ITエンジニアの本棚

ITエンジニアに有益な書籍や勉強法について紹介していくサイトです。

gRPCとProtocol Buffersとenum

スポンサード リンク

lifeliteracy.hatenablog.com

前回は、helloworldをそのまま動かしてみたので、今回は少し中身を書き換えて試してみた。

helloworldの仕様

元々のhelloworldサンプルの仕様は、

  • clientは、任意のnameを引数にして、serverにリクエストを送る
  • serverは、Hello + nameの文字列をclientに返す
  • clientは、返ってきた文字列を出力

というものだったが、以下のように変更した。

  • clientは、任意のnameを引数にして、serverにリクエストを送る(これは今までと同じ)
  • serverは、nameがserver側に設定済みの文字列かどうかを確認する
    • 設定済の場合は、Hello + nameの文字列と、OKというstatusを返す
    • 存在しない場合は、Who are you?という文字列と、UNKNOWNというstatusを返す
  • clientは、返ってきたstatusに応じて、出力するメッセージを変更する

helloworld.protoの変更

protocol buffersでは、enumも使えるので、返す可能性のあるstatusを以下のように設定した。

diff --git a/examples/helloworld/helloworld/helloworld.proto b/examples/helloworld/helloworld/helloworld.proto
index c3ddd4a..aa53891 100644
--- a/examples/helloworld/helloworld/helloworld.proto
+++ b/examples/helloworld/helloworld/helloworld.proto
@@ -46,7 +46,15 @@ message HelloRequest {
   string name = 1;
 }
 
+enum MessageStatus {
+  OK = 0;
+  ERROR = 1;
+  UNKNOWN = 2;
+}
+
 // The response message containing the greetings
 message HelloReply {
   string message = 1;
+  MessageStatus status = 2;
+
 }

serverの変更

予め"world", "japan"というnameだけ受け入れるように準備し、それ以外がきた時は、UNKNOWNというstatusを返すように変更。存在するかどうかの確認にmapを利用しているが、より適切な実装の仕方があるかもしれない。

diff --git a/examples/helloworld/greeter_server/main.go b/examples/helloworld/greeter_server/main.go
index 162cf90..4af4528 100644
--- a/examples/helloworld/greeter_server/main.go
+++ b/examples/helloworld/greeter_server/main.go
@@ -50,12 +50,31 @@ const (
 // server is used to implement helloworld.GreeterServer.
 type server struct{}
 
+var (
+       valid map[string]pb.MessageStatus
+)
+
 // SayHello implements helloworld.GreeterServer
 func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
-       return &pb.HelloReply{Message: "Hello " + in.Name}, nil
+       v, ok := valid[in.Name]
+       if ok {
+               return &pb.HelloReply{
+                       Message: "Hello " + in.Name,
+                       Status:  v,
+               }, nil
+       }
+       return &pb.HelloReply{
+               Message: "Who are you?",
+               Status:  pb.MessageStatus_UNKNOWN,
+       }, nil
 }
 
 func main() {
+       valid = map[string]pb.MessageStatus{
+               "world": pb.MessageStatus_OK,
+               "japan": pb.MessageStatus_OK,
+       }
+
        lis, err := net.Listen("tcp", port)
        if err != nil {
                log.Fatalf("failed to listen: %v", err)

clientの変更

clientは、replyのstatusを確認しUNKNOWNの場合は、異なるメッセージを出力するように変更。

diff --git a/examples/helloworld/greeter_client/main.go b/examples/helloworld/greeter_client/main.go
index a451c99..21e9e6b 100644
--- a/examples/helloworld/greeter_client/main.go
+++ b/examples/helloworld/greeter_client/main.go
@@ -65,5 +65,9 @@ func main() {
        if err != nil {
                log.Fatalf("could not greet: %v", err)
        }
-       log.Printf("Greeting: %s", r.Message)
+       if r.Status == pb.MessageStatus_UNKNOWN {
+               log.Printf("UNKNOWN: %s", r.Message)
+       } else {
+               log.Printf("Greeting: %s", r.Message)
+       }
 }

実行

serverに送るnameは、client実行時の引数で変更することが可能(デフォルトはworld)なので、次のように実行すると、

# go run examples/helloworld/greeter_server/main.go
# go run examples/helloworld/greeter_client/main.go
2018/01/04 21:06:25 Greeting: Hello world
# go run examples/helloworld/greeter_client/main.go japan
2018/01/04 21:06:43 Greeting: Hello japan
# go run examples/helloworld/greeter_client/main.go test
2018/01/04 21:07:00 UNKNOWN: Who are you?

無事、nameに応じて異なるメッセージが出力されることが確認できた。

まとめ

server/client間でやり取りするstatusを.proto fileで定義して、そこから生成した.pb.goを利用してserver/clientのコードをそれぞれ実装すれば、誤ったstatus codeを期待することもなくなるし良さそう。

gRPCは、streaming処理ができるようなので次はそれを試してみようと思う。