gRPCサーバーから"unknown service"エラーが返ってきた時の対処方法について

最近流行りのgRPC.
スキーマ言語であるProtocol Buffersによってデータをシリアライズ化する事ができ、
これまで主流だったjson通信よりもより高速に通信を行えるとして、
マイクロサービス開発で採用されるケースが増えている。

この間gRPCで構築したクライアント・サーバー間の通信をやろうとした時にタイトルの通りのバグが発生し
だいぶ手こずったので、忘備録として原因と解決策を残しておこうと思う。

## ソースコード
ソースはこちら。
https://github.com/yzmw1213/PostService

## やろうとした事

goで書いたgRPCサーバーで、以下２つのサービスを実装する。
 - 投稿サービス
 - 投稿につけるタグの管理サービス(マスタデータとしての扱い）
 

そして、typescriptで実装している client側のコードから、上のサービスにリクエストを送り処理を行う。

``` client.ts
import { Tag, CreateTagRequest } from "~/grpc/tag_pb"
import { TagServiceClient } from "~/grpc/TagServiceClientPb"

post() {
  const client = new TagServiceClient(
    "http://localhost:8080", {}, {}
  )
  
  const request = new CreateTagRequest()
  var tag = new Tag()
  tag.setTagId(postTag.tagID)
  tag.setTagName(postTag.tagName)
  tag.setStatus(postTag.status)
  request.setTag(tag)
  // TagServiceのcreateTagメソッドにリクエストを送る
  client.createTag(request, {}, (err, res) => {
    if (err != null) {
      console.log(err)
    }
    console.log(res)
  })
}
```
上記のようにして、クライアント側からタグサービスにタグ作成のリクエストを行った際、次のエラーが起こった。

```
{ code: 12, message: "unknown service post_grpc.TagService" }
```

## 解決策
 code: 12 は何の事や...と思って公式の[git](https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto "git")を見てみた。

すると、
>  // HTTP Mapping: 501 Not Implemented
  UNIMPLEMENTED = 12;

要するに、
「そのサービス、実装されてないで」 っていう意味の内容だった。


　そこで、呼び出しを行っているサービスがgRPCサーバーに登録されているかどうか確認する。

grpcサーバーの動作確認には[grpcurl](https://github.com/fullstorydev/grpcurl "grpcurl")を使うといい。

この記事に色々詳しい事が書かれていた。
https://qiita.com/yukina-ge/items/a84693f01f3f0edba482

例えばポート50051番でgRPCサーバーを構築しているとすると、以下のように叩くと良い。



```
# ポートに登録されているサービスの一覧
$ grpcurl -plaintext localhost:50051 list
grpc.reflection.v1alpha.ServerReflection
post_grpc.PostService
```
ServerReflectionとPostServiceは登録されているようだ。
あれ、じゃあTagServiceは...と思ってサーバー側のコード読んだら明らかなミスに気がついた。

``` server.go
package grpc

import (
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"

	"github.com/yzmw1213/PostService/grpc/post_grpc"
	"github.com/yzmw1213/PostService/usecase/interactor"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

type server struct {
	PostUsecase interactor.PostInteractor
	TagUsecase  interactor.TagInteractor
}

// NewPostGrpcServer gRPCサーバー起動
func NewPostGrpcServer() {
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	server := &server{}

	s := makeServer()
     // PostServiceを serverに登録
	post_grpc.RegisterPostServiceServer(s, server)

     // TagServiceの登録が抜けている!!!


	// Register reflection service on gRPC server.
	reflection.Register(s)
	log.Println("main grpc server has started")

	go func() {
		if err := s.Serve(lis); err != nil {
			log.Fatalf("failed to serve: %v", err)
		}
	}()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)

	// Block until a sgnal is received
	<-ch
	fmt.Println("Stopping the server")
	s.Stop()
	fmt.Println("Closing the client")
	lis.Close()
	fmt.Println("End of Program")
}

func makeServer() *grpc.Server {
	s := grpc.NewServer(
		grpc.UnaryInterceptor(grpc.UnaryServerInterceptor(transmitStatusInterceptor)),
	)

	return s
}

```

post_grpc.RegisterPostServiceServer で、PostServiceを登録はしているが
TagServiceも同様にgRPCサーバーに登録しなければならない。

次のコードを追加し、解決した。

``` server.go
     // PostServiceを serverに登録
	post_grpc.RegisterPostServiceServer(s, server)
	// 以下を追加
	// タグサービス登録
	post_grpc.RegisterTagServiceServer(s, server)
```

## 振り返り

今回、原因の特定にかなり時間を費やしてしまった。
clientとserverの間にenvoy Proxyを置いており、proxyの方に気を取られまくっていたので。。
悩んであれこれ試した割にはだいぶ初歩的なミスだった...。
[grpcurl](https://github.com/fullstorydev/grpcurl "grpcurl")、これからは初手から使っていこう。

## 補足 
マイクロサービス運用を想定しているgRPCサーバーの構築では、
1 service / 1 server の前提で書かれている記事が数多い印象があるが、必ずしも
全てのサービス毎に細くサーバーを分ける分ける必要もなく、上で実装しているように
それぞれRegisterServiceすれば運用に支障は無いと思っている。

関連性の高いサービス（たとえば、ユーザー登録サービスと認証サービスとか）は
この様に同一サーバーでの運用することが十分可能だと思う。

今後、認証サービスも書く予定なので試してみようと思う。

## 参考記事
[gRPCサーバーの動作確認をgrpcurlでやってみた](https://qiita.com/yukina-ge/items/a84693f01f3f0edba482)