Hello, gRPC!!

## はじめに
この記事は[ユニークビジョン株式会社 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/uniquevision)の11日目の記事です。

コンテナを中心とした開発が主流になっていくなかで、コンテナ間の通信はどうするのかという問題がつきまといます。
普通にAPIとして実装しても良いと思いますが、Googleが開発しているgRPCを活用すれば通信方法やAPIのドキュメントの準備の手間を軽減したり、HTTP/2を利用した良好なパフォーマンスなど様々なメリットを享受できます。

社内への宣伝を兼ねて、今回gRPCのチュートリアルをやってみたいと思います。

## gRPC
Google社が開発しているRPCフレームワークです。.protoを定義することで、そこから様々な言語のコードを生成し、サーバにメソッドを定義することで、異なる言語間で通信が可能となります。
公式のドキュメントに対応言語のチュートリアルなどがまとまっており、今回はここ[^1]を参考にしつつ実装していきます。

## Hello, World
### 準備
####　環境
* Go(今回は1.10を使用します)
  * Goのセットアップに関してはGo公式のドキュメントやその他の記事を参照してください
  * $GOPATH定義までしてあるといいとおもいます

#### ライブラリ
gRPCと併せて今回goで実装するのでGoPluginも

``` shell
$ go get -u google.golang.org/grpc
$ go get -u github.com/golang/protobuf/protoc-gen-go
```


#### ディレクトリ構成
適当なディレクトリ($GOPATH以下が望ましい)に以下のような形で準備します。

```
.
├── client
│   └── main.go
├── hellogrpc
│   └── hellogrpc.proto
└── server
    └── main.go
```

### .protoの定義
今回はhellogrpcディレクトリ以下に、サーバ - クライアント間のインタフェースを定義するprotoファイルを設置します。

このprotoファイルを元にgRPCがサポートしている各言語の実装を生成します。

``` proto:hellogrpc.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.tokikokoko.hellogrpc.server";
option java_outer_classname = "HelloGrpc";

package HelloGrpc;

// The greeting service definition.
service HelloGrpc {
	// Sends a greeting
	rpc GreetServer (GreetRequest) returns (GreetMessage) {}
}

message GreetRequest {
	string name = 1;
}

message GreetMessage {
	string msg = 1;
}
```

上記のファイルで注目してほしいのは`package HelloGrpc;`以下で、

``` proto
// The greeting service definition.
service HelloGrpc {
    // Sends a greeting
    rpc GreetServer (GreetRequest) returns (GreetMessage) {}
}
```

`service`内で今回実装するgRPCサーバにどのようなメソッドを実装するのかを定義します。

`rpc`に続く文がserviceのメソッドとなり、ここでは`GreetServer`というメソッドを定義しています。これは`GreetRequest`型の`message`を引数として`GreetMessage`型の`message`を返すという定義です。

``` proto
message GreetRequest {
	string name = 1;
}

message GreetMessage {
	string msg = 1;
}
```

`message`に続く文でメッセージ型を定義しており、構造体のように扱えます。

ここでは

* `string`型の`name`を持った`GreetRequest`
* `string`型の`msg`を持った`GreetMessage`

を定義しています。

### .protoをコンパイル
定義した.protoファイルはコンパイルすることで、各言語の実装を出力します。出力されたコードを`import`することで定義したインタフェースを実装、呼び出すことができます。

今回定義したprotoファイルで、Go言語のコードを出力するためには、

``` bash
$ protoc -I hellogrpc/ hellogrpc/hellogrpc.proto --go_out=plugins=grpc:hellogrpc
```

を実行することで、`hellogrpc/hellogrpc.pb.go`が出力されます。

### サーバとクライアントを実装する
出力されたコードをimportして、実際にサーバとクライアントを実装していきます。

``` go:server/main.go
package main

import (
    "context"
    "fmt"
    "net"

    "google.golang.org/grpc"
    "log"

    // .protoから生成されたコードをimportしている
    // 今回は筆者の$GOPATH内に作成したので適宜プロジェクトを作成したパスに合わせる
    pb "github.com/tokikokoko/hello_grpc/hellogrpc"
)

// gRPC server struct
type server struct {
}

// .protoで定義したGreetServerを定義している
func (s *server) GreetServer(ctx context.Context, p *pb.GreetRequest) (*pb.GreetMessage, error) {
    log.Printf("Request from: %s", p.Name)
    return &pb.GreetMessage{Msg: fmt.Sprintf("Hello, %s. ", p.Name)}, nil
}

func main() {
    // gRPC
    port := 10000
    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    log.Printf("Run server port: %d", port)
    grpcServer := grpc.NewServer()
    // メソッドを定義
    pb.RegisterHelloGrpcServer(grpcServer, &server{})
    // gRPCサーバを公開
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
```

``` client/main.go
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    // .protoから生成されたコードをimportしている
    // 今回は筆者の$GOPATH内に作成したので適宜プロジェクトを作成したパスに合わせる
    pb "github.com/tokikokoko/hello_grpc/hellogrpc"
)

const (
    address = "localhost:10000"
)

func main() {
    // gRPCサーバへのconnectionを作成
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    // connection終了処理
    // 関数終了後に実行される
    defer conn.Close()

    c := pb.NewHelloGrpcClient(conn)

    name := "ほげ太郎"

    // タイムアウトを設定
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    // .protoで定義したサーバのGreetServerを呼び出している
    r, err := c.GreetServer(ctx, &pb.GreetRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Response: %s", r.Msg)
}
```

### 実行結果
``` shell:サーバ側
$ go run server/main.go
2018/12/11 00:37:24 Run server port: 10000
2018/12/11 00:37:26 Request from: ほげ太郎
```

``` shell:クライアント側
$ go run client/main.go
2018/12/11 00:37:26 Response: Hello, ほげ太郎.
```

## まとめ
超簡単なgRPCサーバ&クライアントを実装しました。gRPCの概要を理解してもらえれば幸いです。超ぬるい記事ですが、初投稿なのでお許しくださいorz

明日も私が担当です。CI/CDするとか言っていましたがgRPCの導入の話が存外に長くなってしまったので、今日に引き続きgRPCのstreamを使った簡素なチャットツールを実装しようと思います。

[^1]: [Go Quick Start](https://grpc.io/docs/quickstart/go.html)