gRPCメモ

自身のメモとしてgRPCをまとめてみました。

# gRPCとは
protocol buffersをMessage interchange format（メッセージのI/Oに使うための形式）として使えるリモートプロシージャコールシステムです。

### protocol buffersとは
IDL:Interface Definition Language (インタフェース定義言語)を用いたファイルフォーマットです。

### gRPCで何ができるのか
クライアントが別のマシンにあるメソッドをまるでローカルにあるかのように使えるようになります。
下の図のように様々なプログラミング言語に対して実装できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270964/7b45a2d4-117f-b81c-5b49-3a85cf1f50fa.png)

#### 雑に言うと
今までjsonなどを介してAPIを叩いていたようなところを、もっと確固たる定義をもった上でより高パフォーマンスでやりとりができる…という感じでしょうか。

# protocol bufferの定義の仕方
やりとりするmessageの構成は、以下のようにname-valueでfieldを定義します。

```person.proto
message Person {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}
```

gRPC Serverの定義はrpcメソッドを使って行います。

```greeter.proto
// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
```

# protoファイルののコンパイル
上のgreeter.protoをGo言語ようにコンパイルする場合は以下のようなコマンドを叩きます。
`protoc --go_out=plugins=grpc:./ ./greeter.proto`

このコンパイルでClient側のメソッドもServer側のメソッドも作成されます。


もし、何かのディレクトリの中にあるすべての.protoをコンパイルしたい場合は、以下のような感じでワイルドカードを使うこともできます。
`protoc --go_out=plugins=grpc:./ ./*.proto`

もし、どこかのディレクトリに入れたいという場合は、grpc:後のところを書き換えます。
以下の場合はprotoディレクトリの中にコンパイルされたデータが書き出されます。
`protoc --go_out=plugins=grpc:proto ./*.proto`

もし、どこかのディレクトリからどこかのディレクトリに書き出したい場合は、以下のような書き方になります。
`protoc -I protobuf --go_out=plugins=grpc:proto protobuf/*.proto`
-I が無いとprotoディレクトリ配下にprotbufディレクトリが作成され、そのなかにコンパイルされたデータが書き出されます。
-I はimportの基点となる場所をしてしてやるオプションと考えてもらえば良いかもしれません。

`--go_out=plugins=grpc:`と書いているのですが、protocol buffer自体は別にgRPCのためだけのものではないので、このようにgRPCのための書き出しですよーというオプションを書いておく必要があります。

Ruby用にコンパイルしたい場合は --go_outを書き換えます。（複数書けば複数の言語のファイルを同時に書き出せます。）
`protoc -I protobuf --ruby_out=plugins=grpc:proto protobuf/*.proto`


# gRPCサーバーのリクエストとレスポンスの種類
種類は4つあります。

## Unary RPC
一番シンプルな方法で、単一のメッセージのリクエストに対して、単一のメッセージをレスポンスで返す方法です。

```greeter.proto
rpc SayHello(HelloRequest) returns (HelloResponse) {
}
```

## Server streaming RPC
単一のメッセージのリクエストに対して、複数のメッセージをレスポンスで返す方法です。
例えばですが、何かのデータの一覧が欲しいなどと言うときに、この方法を使えたりします。

```greeter.proto
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse) {
}
```

## Client streaming RPC
複数のメッセージのリクエストを受けた上で、単一のメッセージのレスポンスを返します。
```greeter.proto
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
```

## Bidirectional streaming RPC
これはリクエストもレスポンスも複数のメッセージをレスポンスを扱う方法ですが、
すべてのメッセージを待って、すべてのメッセージを書き出して返すことも、一つメッセージを受け取る毎に、メッセージを書き出していき、最後にまとめてメッセージを返すことも、サーバー側の実装次第で返ることができます。

```greeter.proto
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse) {
}
```

# サーバー側の実装
主な処理は以下のような感じです。


```main.go
package main
import (
	pb "github.com/{リポジトリ名}/{デフォルトならproto}"
	"flag"
	"net"
	"log"
	"sync"
	"fmt"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/testdata"
)

var (
	tls        = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP")
	certFile   = flag.String("cert_file", "", "The TLS cert file")
	keyFile    = flag.String("key_file", "", "The TLS key file")
	jsonDBFile = flag.String("json_db_file", "", "A json file containing a list of features")
	port       = flag.Int("port", 10000, "The server port")
)

func main() {
	flag.Parse() // 引数を入れて実行したい時用にflagを使ってます。
	
	// hostとportを設定します。
	lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	// 必要であればcredentialの設定をします。
	var opts []grpc.ServerOption
	if *tls {
		if *certFile == "" {
			*certFile = testdata.Path("server1.pem")
		}
		if *keyFile == "" {
			*keyFile = testdata.Path("server1.key")
		}
		creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
		if err != nil {
			log.Fatalf("Failed to generate credentials %v", err)
		}
		opts = []grpc.ServerOption{grpc.Creds(creds)}
	}

	// gRPCのサーバーを初期化
	grpcServer := grpc.NewServer(opts...)
	// protocでコンパイルされたコードからサーバーを登録するメソッドを実行
	pb.RegisterGreeterServer(grpcServer, newServer())
	// サーバーを起動
	grpcServer.Serve(lis)
}
```
上の記述で書いていないのですがnewServer()という関数が重要なのですが、Register*Serverの第二引数にはprotoのServer内で定義したメソッドを持ったinterfaceを入れる形になっています。

その為に以下のような形の定義もmainに入れておきます。（例えば、Server streaming RPCだけ定義している場合）

```main.go
type greeterServer struct {}
// nilかerrを返せばstreamingが終了します。
func(s *greeterServer) LotsOfReplies(req *pb.LotsOfRepliesRequest, stream pb.Greeter_LotsOfRepliesServer) error {
	hs := []pb.HelloReply{
		pb.HelloReply{ Name: "ohayo" },
		pb.HelloReply{ Name: "konnichiwa" },
		pb.HelloReply{ Name: "konbanwa" },
	}
	for _, h := range hs {
		if err := stream.Send(&h); err != nil {
			return err
		}
	}
	return nil
}

func newServer() *greeterServer {
	s := &greeterServer{}
	return s
}
```

# その他
## repeated
protoの定義の中でmessageの中に配列をいれたい場合はRepeatedが使えます。

```greeter.proto
message HelloResponse {
  repeated string name = 1;
}
```

こうするとnameの配列を返す事ができます。
複数のmessageを返したい場合はstreamでmessageの中で処理できるならrepeatedというところでしょうか。

## oneof
messageに入るのがstringの可能性もあるし、int32の可能性もある…なんて場合に使えます。

```greeter.proto
message HelloResponse {
    oneof name {
        string text = 1;
        int32 id = 2;
    }
}
```

# リファレンス
- [gRPC Quick Start](https://grpc.io/docs/quickstart/)
- [Protocol Buffers Language Guide](https://developers.google.com/protocol-buffers/docs/proto)