gRPCを使った簡易的なマイクロサービスを作ってみた

# gRPCとは

2015/2 にGoogleが公開したGoogle内でも使用されているRPCフレームワークであり、下記の恩恵をうけることができます

- Protocol BufferのIDLを書くことによって、通信形式の型が保証された通信を行うことができる
- gRPCをサポートしている言語であれば、異なる言語間でも通信が可能(C++, Java, Go, Python, Ruby, Node.js, Android Java, C#, Objective-C, PHP）
- HTTP/2で通信を行うためストリームの多重化、フロー制御等ができる

弱点としては、

- 実装難度が高い点、
- protocが動く環境に制限されるためフロントエンドのjsでは動かない

というものがあるため、

- サーバーサイドのマイクロサービス間の通信
- 長期運用や規模の大きいサービスで通信の保証をする必要がある

ものに適している通信方式と思います

# 今回やってみたこと

検証ということで、ソシャゲでよくあるガチャの簡単なマイクロサービスを作成してみました。

1. 複数のCardをサーバーに送る
2. その中からランダムに一つ選ぶ（今回は単純にランダムなものを選んでいます)（本来のソシャゲの場合はかくりｔ..おっと誰か来たようだ）
3. サーバーから一つカードを返却し、返答ステータスも返却します(今回は例外処理は書いてないので常に同じRetCodeが返ってきます）

この実装によって

- 基本的なgRPCの実装
- 複数言語でのgRPCの挙動
- 配列的な型のパターン
- Request Responseで同じ型を使うパターン

を確認します。
ソースコードはこちら　https://github.com/kotamat/grpc-gacha

## フロー

![スクリーンショット 2016-09-22 18.34.37.png](https://qiita-image-store.s3.amazonaws.com/0/18556/ab7960d5-a659-5113-54da-5c77c1768fe0.png)

## 各種解説

### ProtocolBuffer

設定ファイルは下記のようになっています。

```protobuf
syntax = "proto3"; // PBのバージョン

package gacha; // 全体の名前空間

service Gacha{ 
    rpc Lottery (Request) returns (Response) {}
}

message Card {
    string name = 1;
}

message Request {
    repeated Card cards = 1; // 配列の指定
}

message Response {
    Card card = 1; //他のmessageをパラメータとする
    int32 ret_code = 2;
}
```

`service` の中の rpcに続くものは、`実行関数` ( `引数` ) returns ( `返り値` ) {}
という順番になっており引数、返り値の型を宣言しているものが `messsage` に続く物となっています。
`message` にはパラメータを複数指定することができ、また他の`message`をパラメータとすることもできます
パラメータに配列を指定したい場合は、`repeated`を添字にすることにより、0個以上のパラメータを受け取ることができます。

パラメータは `添字` `型` `パラメータ名` = `処理インデックス`;
という順番となっており添字はproto3では`optional`と`repeated`が使用可能です。 ( `required`は後方互換性の問題から削除されました )
ｇRPCでは通信のオーバーヘッドを防ぐために、処理インデックスを元に通信を行うため、処理インデックス(整数値)を指定する必要があります。

#### ProtocolBufferのビルド

ProtocolBufferは各言語ごとにファイルを生成し、実際の処理で使用できるようにする必要があります。
ビルドは `protoc` というOS依存のファイルのインストールと、
言語別のビルド用プラグインをインストールする必要があります。

Golangの場合は 

```bash
$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
```

でインストールできます。他の言語は公式サイトを御覧ください
http://www.grpc.io/docs/quickstart/go.html


## 実際の処理(Golang)

### サーバーサイド

各種言語によって実装方法はことなりますが、
基本的に

1. Listen処理、サーバーの立ち上げ、
2. ProtocolBufferで定義した関数の処理

を記載するだけです。

#### Listen処理

```go
func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterGachaServer(s, &server{})
	s.Serve(lis)
}
```

#### ProtocolBufferで定義した関数の処理

```go
func (s *server) Lottery(ctx context.Context, in *pb.Request) (*pb.Response, error) {
	if len(in.Cards) < 1 {
		return &pb.Response{Card:nil, RetCode:0}, errors.New("empty cards")
	}
	rand.Seed(time.Now().UnixNano())
	chosenKey := rand.Intn(len(in.Cards))
	return &pb.Response{Card: in.Cards[chosenKey], RetCode: 1}, nil
}
```

クライアントからの情報は `*pb.Request` に乗っかっているのでそれをゴニョゴニョして `*pb.Response` に返せばオッケーです。

### クライアントサイド

クライアントサイドも基本的には下記の処理を書きます。

1. gRPCコネクションの作成
2. 送信データの整形
3. 関数の実行

#### gRPCコネクションの作成

```go
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGachaClient(conn)
```

#### 送信データの整形

```go
	// define cards
	cards := []*pb.Card{
		&pb.Card{Name: "card1"},
		&pb.Card{Name: "card2"},
	}
```

#### 関数の実行

```go
	r, err := c.Lottery(context.Background(), &pb.Request{Cards: cards})
	if err != nil {
		log.Fatalf("could not get card %v", err)
	}
	log.Printf("gain card: %v", r.Card.Name)
```

## 実行

予めサーバーを立ち上げておいた上で、
クライアントを実行します。

```bash
go run server/server.go
```

```bash
go run client/client.go
```

## まとめ

‐ マイクロサービスを作る際に通信のフォーマットを保証する方法としてgRPCはある程度担保してくれる
- 多言語間の通信もしやすい

## 課題

- 環境整備(特にprotocol bufferをビルドするツールのインストール)が言語によっては大変だった。
    - PHPでもやろうとしたが、DockerHubに公開されている Dockerfileだけだと protoc-gen-phpが入っていなかった(?)ため、一旦断念.. https://github.com/grpc/grpc-docker-library/tree/master/0.11/php
- 本番環境で適応するには、例外処理や複雑な処理に対する知見を貯める必要がありそう。