GoとDockerでLet's try gRPC

**※ この記事は2019年11月7日に作成した [GoとDockerでLet's try gRPC - LiBz Tech Blog](https://tech.libinc.co.jp/entry/2019/11/07/111548) と同じ内容です**

# はじめに
こんにちは！エンジニアの渡邊です。早いもので、11月でLiBに入社して丸1年がたちました。
このブログへの投稿も４回目になります。

前回の「[とってもRailsライクなサーバーレスフレームワーク「Ruby on Jets」を本番環境に導入した話](https://tech.libinc.co.jp/entry/2019/07/12/113215)」では、jetsの開発者である**[tongueroo氏](https://twitter.com/tongueroo)**や、Rubyの生みの親**[まつもとゆきひろ氏](https://twitter.com/yukihiro_matz)**をはじめ、多くの方にシェアをしていただき大変励みになりました！
みなさんありがとうございました。

今回はGoogleが開発したRPCフレームワーク**[gRPC](https://grpc.io/)**について書こうと思います。

<img width="500" alt="20191106144030.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760714/a5279b73-1f96-2db7-7af9-03a7b3a7dae7.jpeg">


# gRPCとは

Google社内で使用されているstubbyというRPC(Remote Procedure Call)フレームワークをオープンソース化したものです。

### gRPCの特徴

- **Protocol Buffers**(xml,jsonのようなインターフェース記述言語)を利用した高速かつ低用量、異なる言語間でも型保証された通信
- サーバ/クライアント用**ソースコードの自動生成**
- 多言語対応(C++, Java, Go, Python, Ruby, Node.js, C#, Objective-C, PHP, etc..）
- 通信プロトコル**HTTP/2による通信**
	- Streamingを用いた双方向通信(従来の通信は1Request-1Response)

上記のような特徴から、**gRPCはマイクロサービス化されたシステム間の接続やデータ転送についての課題を解決することができ、効率良く通信を行う**ことができます。

<img width="500" alt="20191106164527.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760714/a51c1968-1234-ec82-c31f-42f2af030910.png">

### gRPCが解決するマイクロサービスの課題

- 複数のシステム間でインターフェースが統一されている必要がある

	- システムは頻繁に変更があるものなので全システム間の整合性をとることが難しい

		**-> ProtocolBuffer(.protoファイル)からサーバー/クライアントのソースコードを自動で生成でき、言語も複数選択できる**
	
	- 複数のシステムの仕様を把握するためのドキュメント(仕様書, wiki, Swagger, etc..)更新漏れ、記載ミス

		**-> サービス間の通信はprotoファイルから生成されるため、protoファイルが正しい仕様になる(ソースコードはprotoファイルを元に生成されるため、漏れやミスが発生しない)**

-  APIリクエストが頻発するためパフォーマンスが劣化しやすくなる
	- 複数のシステムに分散されたリソースをそれぞれ取得しに行く必要があるためどうしてもリクエスト回数が増える

		**-> HTTP/2では一度のTCPのコネクションで複数のリクエスト, レスポンスを取扱うことができる**
	
	- HTTP/1では一度のコネクションで取得できるリソースは一つという仕様(ブラウザ側で複数のTCPを同時に貼るとしてもコネクション数に制限がある)

		**-> HTTP/2での通信のため、コネクションは捨てられることなく初回接続時のコネクションを使い続けられる**

<img width="200" alt="20191106164702.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760714/b9365e02-dd0f-6b5d-d0fa-ec28f4f3a0f7.png">

### gRPCの課題

当然メリットばかりではなくデメリットもあります。
通信規格がHTTP/2ということは、**対応していないブラウザやロードバランサはリクエストを受けることが出来ない**ということです。

ブラウザに関しては[grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)や[grpc-web](https://github.com/grpc/grpc-web)などのリバースプロキシを行ってくれるライブラリを利用すれば問題ないのですが、
サービス間通信はHTTP2プロトコルで、ブラウザとサーバ間通信のみHTTP1プロトコルになってしまうので混在することに違和感を覚える人もいるのではないでしょうか。


gRPCサーバーの動作確認もやや面倒です。
従来のAPIのような動作確認はcurlで行えましたが、gRPCではcurlは使えません。
(最近はgRPCでもcurlライクに動作確認ができる[gRPCurl](https://github.com/fullstorydev/grpcurl)や、gRPC用のGUIクライアントである[gRPC UI](https://qiita.com/hinastory/items/131cb603af34e3235ccf)というツールがあるようです)

<img width="200" alt="20190711145732.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760714/f7087b3f-0a8f-321e-3a9f-16c196ba6a82.png">

# Let's try gRPC

※コード周りは下記の記事のものを利用させていただきました!

[Goで始めるgRPC入門](https://qiita.com/marnie_ms4/items/4582a1a0db363fe246f3)
[GolangでgRPCを試してみる](https://techblog.ap-com.co.jp/entry/2019/07/31/134900)

<img width="200" alt="20191106164848.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760714/6f26cade-7f94-be48-646e-0d670040c24a.png">

## 1. 準備

- gRPC
- protoc(.protoファイルからコード生成をするコンパイラ)
- protoc-gen-go(protocのGo用プラグイン)

上記の３つをインストールします。

**gRPC**

```
$ go get -u google.golang.org/grpc
```

**protoc**

OSによって異なります。[こちら](https://github.com/protocolbuffers/protobuf/releases)からインストールしてください。

**protoc-gen-go**

```
$ go get -u github.com/golang/protobuf/protoc-gen-go
```

**Dockerfile**

今回はDockerを使用しますのでDockerfileとdocker-compose.ymlも用意します。

```Dockerfile
FROM golang:1.13.1  
  
RUN apt-get update && apt-get install -y unzip  
  
# Install protobuf  
# @see https://github.com/yoshi42662/go-grpc/blob/master/server/Dockerfile  
RUN mkdir -p /tmp/protoc && \  
  curl -L https://github.com/protocolbuffers/protobuf/releases/download/v3.10.0/protoc-3.10.0-linux-x86_64.zip > /tmp/protoc/protoc.zip && \  
  cd /tmp/protoc && \  
  unzip protoc.zip && \  
  cp /tmp/protoc/bin/protoc /usr/local/bin && \  
  chmod go+rx /usr/local/bin/protoc && \  
  cd /tmp && \  
  rm -r /tmp/protoc  
  
WORKDIR /study-grpc  
COPY . /study-grpc  
  
RUN go get -u google.golang.org/grpc  
RUN go get -u github.com/golang/protobuf/protoc-gen-go
```

**docker-compose.yml**

とりあえずコンテナが起動していれば良いので`command: bash`にしています

```docker-compose.yml
version: '3.7'  
  
services:  
  study-grpc:  
    build: .  
    container_name: "study-grpc"  
	ports:  
      - 1234:1234  
    volumes:  
      - .:/study-grpc  
	command: bash
	tty: true
```

## 2. protoファイルの作成

protoファイルにインターフェースを定義しコードを生成します。  
`pb/cat.proto` を作成しました。

```proto
syntax = "proto3";  
  
service Cat {  
    rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {}  
}  
  
message GetMyCatMessage {  
    string target_cat = 1;  
}  
  
message MyCatResponse {  
    string name = 1;  
    string kind = 2;  
}
```

`syntax = "proto3"` を記載し忘れるとproto2として解釈されるので注意が必要です。  
gRPCでは一般的にproto3を利用します。proto2とproto3の違いは[こちら](https://qiita.com/ksato9700/items/0eb025b1e2521c1cab79#proto2%E3%81%A8proto3%E3%81%AE%E9%81%95%E3%81%84)の記事がわかりやすかったです。


`string name = 1` の数字の部分はタグナンバーです。タグナンバーはフィールドを区別する際に利用されます。一度採番したら変えない方がよいとされているので変更がある場合は新たに採番します。


次にコンテナ内に入りprotoファイルをコンパイルし、ソースコードを生成します。

```
# コンテナ起動
$ docker-compose up

# コンテナの中に入る
$ docker exec -it study-grpc bash

# protocコマンド実行
$ protoc --go_out=plugins=grpc:. ./pb/cat.proto
```

`pb/cat.pb.go`が生成さていればOKです。

## 3. server側の処理

`server.go`を作成します。

```go
package main  
  
import (  
	"context"  
	"errors"
	"google.golang.org/grpc"
	"log"
	"net"
	cat "study-grpc/pb"  
)  
  
type myCatService struct{}  
  
func (s *myCatService) GetMyCat(ctx context.Context, message *cat.GetMyCatMessage) (*cat.MyCatResponse, error) {  
	switch message.TargetCat {  
	case "tama":  
		return &cat.MyCatResponse{  
			Name: "tama",  
			Kind: "Maine Coon",  
		}, nil  
	case "mike":  
		return &cat.MyCatResponse{  
			Name: "mike",  
			Kind: "Norwegian Forest Cat",  
		}, nil  
	default:  
		return nil, errors.New("Not Found YourCat..")  
	}  
}  
  
func main() {  
	port, err := net.Listen("tcp", ":1234")  
	if err != nil {  
		log.Println(err.Error())  
		return  
	}
	s := grpc.NewServer()  
	cat.RegisterCatServer(s, &myCatService{})  
	s.Serve(port)  
}
```

## 4. client側(リクエスト)の処理

`client.go`を作成します。

```go
package main  
  
import (  
	"context"  
	"fmt"
	"google.golang.org/grpc"
	"log"
	cat "study-grpc/pb"  
)  
  
func main() {  
	conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())  
	if err != nil {  
		log.Fatal("connection error:", err)  
	}  
	defer conn.Close()  
  
	client := cat.NewCatClient(conn)  
	message := &cat.GetMyCatMessage{TargetCat: "mike"}  
	res, err := client.GetMyCat(context.Background(), message)  
	if err != nil {  
		log.Fatal(err)  
	}  
	fmt.Printf("result:%s\n", res)  
}
```

## 5. buildして実行

作成した`server.go`と`client.go`をbuildして実行してみましょう。

```
# コンテナの中に入る
$ docker exec -it study-grpc bash

# server.goをbuild & 実行
$ go build server.go
$ ./server

# client.goをbuild & 実行
$ go build client.go
$ ./client

# buildするのが面倒な場合はgo runでもokです
$ go run server.go
$ go run client.go
```

実行結果

```
result:name:"mike" kind:"Norwegian Forest Cat"
```

# 最後に

いかがでしたでしょうか？

名前を聞いただけでは「gRPC? ProtocolBuffer? なんだそれ難しそう、、」と思ってしまいますが実際に手を動かしてみると想像していたものよりは簡単だったのではないでしょうか。

Googleだけでなくいくつもの大手企業が採用している実績がありますし、日本の企業での事例もどんどん増えてきています。

<img width="600" alt="20191106171413.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760714/a3d19adc-8b5b-5dec-337d-87f7c72115b2.png">


マイクロサービス化への課題はいくつもありますが通信部分を解決してくれるgRPCは選択肢のひとつとして、ぜひ覚えておきたいですね。


今回使用したコードは[こちらのリポジトリ](https://github.com/kkwatanabe/study-grpc)にまとめました。