知識ゼロから始める gRPC のサーバーサイド開発

# はじめに

[スターティングgRPC](https://www.amazon.co.jp/dp/B087R87L6Z) という本を読んだので、そのアウトプット記事を書きます。
言語は Go です。

本を読む前の筆者のレベル感は以下であり、想定読者も同様になります。

- Go は書いたことある
- gRPC は 1mm もわからん

本記事では gRPC 未経験者が最低限のサーバーサイド開発をできるレベルに到達することを目標とします。

ベースは [スターティングgRPC](https://www.amazon.co.jp/dp/B087R87L6Z) ですが、最低限の内容のみを抜き出しています。
特に「そもそも gRPC とはどいう技術か？」「既存技術と比較したときのメリット・デリットは？」「gRPC の採用判断はどのようにすべきか？」などは本の方にかなりわかりやすく書かれているため、ぜひ一読をおすすめします。

筆者は Docker でサクっと環境を作り学習しました。
学習時のソースコードは [GitHub にアップロード](https://github.com/HirotoOhria/grpc-demo) しています。

# 本記事の範囲

本記事では gRPC とは何か？から始まり、実際の実装の流れまでを記述します。
実装では .proto ファイルを書き、コードを自動生成し、生成されたインターフェースを実装することになります。
最後に gPRC クライントでの動作確認を行います。

以下、本記事の範囲の詳細になります。

### 含むもの

- gRPC とは何か？
- gRPC の長所は何か？
- .proto ファイルの書き方
- gRPC サーバー実装

### 含まないもの

- PRC とは何か？
- REST との違いは何か？
- gRPC の短所は何か？
- gRPC が対応しているストリーミング形式
- 双方向/双方向ストリーミングの実装方法

くどいようですが、上記はすべて [スターティングgRPC](https://www.amazon.co.jp/dp/B087R87L6Z) に書いてあります。
本当におすすめです。

# gRPC とは何か？

gRPC とは、以下の2つを実現する RPC フレームワークである。

1. 高速な API 通信
1. スキーマ駆動家初

現在、マイクロサービス間の内部通信の実現方法として有力視されている。

# なぜ gRPC を使用するのか？

REST と比較し、2つの長所があるためである。

### ① HTTP/2 による高速通信

gRPC では HTTP/2 を採用しており、 HTTP/1.1 を採用する REST よりも高速である。
HTTP/2 ではデータ形式はバイナリであり、コネクションの接続・切断を都度行わなくて良い点が特徴である。

### ② スキーマ駆動開発による高生産性

gRPC では Protocol Buffers を採用している。
Protocol Buffers ではスキーマを定義し、コードを自動生成する。

スキーマ定義 → 自動生成の仕組みにより、API ドキュメントの管理が不要となる。
スキーマ = API ドキュメントとなるためである。

クラアントがサービスを利用したい場合、スキーマを見ればすべて書いてある。
バックエンドがサービスを追加・変更したい場合、スキーマを書けばコードを自動生成できる。

# .proto ファイルの記述
Protocol Buffers では、スキーマは `*.proto` というファイルに独自の IDL で記述する。
実際の書き方は以下の記事で。

https://qiita.com/hiro_hira/items/b3f64e072e94e99cb554

# protoc によるコード生成

### 前提条件

Go を利用する場合、以下の3つのパッケージが必要である。

- コンパイラ
  - protoc
- コードジェネレータ
  - protoc-gen-go
  - protoc-gen-go-grpc

#### protoc のインストール

Mac の場合、以下でインストールできる。

```shell
$ brew install protobuf
```

#### コードジェネレータのインストール

Go をインストール済みの場合、以下でインストールできる。

```shell
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```

### 生成コマンド

`protoc` コマンドでコードの自動生成が可能である。

```shell
$ protoc --go_out=path/to/output_dir --go-grpc_out=path/to/output_dir --go-grpc_opt=require_unimplemented_servers=false path/to/.proto_file
```

#### Go と gRPC のオプションを指定
Go と gRPC の2つのオプションを指定していることに注意。
Brotocol Buffers は何も Go 専門でもなければ、 gRPC 専門でもない。
そのため、生成先コードとして両方を指定する必要がある。

#### require_unimplemented_servers オプション
デフォルトで `true` である。

英語で調べても情報が出てこないので、正直あまり理解していない。
ざっくりインターフェースが実装されていない場合にエラーを返すメソッドと捉えている。
`true` を指定した場合は実装が求められるが、何を実装すればよいかイマイチわからない。

緊急性もなさそうなのでオプションで off にすることにより対応。

# インターフェースの実装
`ptoroc` コマンドにより生成されたインターフェースを実装するコードを書く。

### ハンドラー
基本的に型は自動生成されているため、行いたい処理を実装するだけでOKである。
コード例は後ほど。

### ハンドラーの登録

ハンドラーを登録し、サーバーにエンドポイントを追加する。

```go
server := grpc.NewServer()
api.RegisterXXXServiceServer(server, handler.NewXXXHandle())

server.Serve()
```

# 簡単なコード例

以下に簡単なコード例を示す。
パッケージ周りはザルであるが、勘弁願いたい。

実際に動くソースコードは [GitHub](https://github.com/HirotoOhria/grpc-demo/tree/main/qiita) に置いている。

### .proto ファイル

```go:user.proto
syntax = "proto3";
package user;

option go_package = "gen/api";

service UserGetService {
  rpc Get(UserRequest) returns (UserResponse) {}
}

message User {
  int32 id = 1;
  string name = 2;
}

message UserRequest {
  int32 id = 1;
}

message UserResponse {
  User user = 1;
}

```

### ハンドラー

```go:user_handler.go
package handler

import (
	"context"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"user/api/gen/api"
)

type userHandler struct{}

func NewUserHandle() *userHandler {
	return &userHandler{}
}

func (h *userHandler) Get(ctx context.Context, r *api.UserRequest) (*api.UserResponse, error) {
	if r.Id == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "ユーザーの ID を指定してください")
	}

	res := &api.UserResponse{
		User: &api.User{
			Id:   24,
			Name: "John",
		},
	}

	return res, nil
}

```

### サーバー

```go:server.go
package main

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

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	"user/api/gen/api"
	"user/handler"
)

const port = 50052

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

	server := grpc.NewServer()
	api.RegisterUserGetServiceServer(server, handler.NewUserHandle())
	// To debug on grpc_cli.
	reflection.Register(server)

	go func() {
		log.Printf("start gRPC server port: %v", port)
		server.Serve(lis)
	}()

	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)
	<-quit
	log.Println("stopping gRPC server...")
	server.GracefulStop()
}
```
# gRPC クライアント
動作確認のために gRPC を叩きたくなります。

ただし、cURL ではレスポンスを可読することはできません。
Protocol Bufffers はデータ形式がバイナリだからです。

そのため、gRPC 専用のクライアントを使用する必要があります。
今回は grpc_cli を使用します。

## エンドポイントを叩く

### grpc_cli のインストール

Mac の場合は以下でインストールできる。

```
$ brew tap grpc/grpc

$ brew install grpc
```

### 叩いてみる

コードの実装例の場合、以下のコマンドでエンドポイントを叩ける。

```
$ grpc_cli call localhost:50052 user.UserGetService.Get 'id: 1'
connecting to localhost:50052
user {
  id: 24
  name: john
}
Rpc succeeded with OK status
```

## スキーマ定義を見る

### サーバーを reflect 可能にする

サーバーを reflection に登録します。

スキーマ定義はデフォルトでは外部呼び出しから見ることはできません。
本番運用では必要ない機能だからです。
この設定をすることで、 gRPC クライアントからスキーマ定義を参照可能になります。

```go
import (
  ...
  "google.golang.org/grpc/reflection"
)

func main() {
  ...
  reflection.Register(server)
}
```

### ls する

`grpc_cli ls` コマンドでスキーマ定義を見ることができます。
ファイルを見たほうが速いか、cli から見たほうが速いかは人によりそうですね。

```shell
$ grpc_cli ls localhost:50052 user.UserGetService -l
filename: user.proto
package: user;
service UserService {
  rpc Get(user.UserRequest) returns (user.UserResponse) {}
}
```

# 終わりに

モダンらしいとは聞きつつ中々手がつけられなかった gRPC の学習・解説をしました。
本では一部古くなっていた箇所も改修してコードにしています。

もし役に立ちましたら LGTM 押して頂けると励みになります。

# 参考文献

- [スターティングgRPC](https://www.amazon.co.jp/dp/B087R87L6Z)