Go言語+gRPCの解説 [ハンズオン]

## はじめに

この記事は、海外サイトhttps://tutorialedge.net/golang/go-grpc-beginners-tutorial/
の内容をベースに一部修正を加えたものです。この記事を読み進めることでGoでシンプルなgRPCクライアントとサーバーを構築することができるようになります。

![スクリーンショット 2019-05-24 7.53.10.png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F221948%2F8bb0f734-4855-7c54-d318-3b4474aa7311.png?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&s=685b5ae9b714b2f9c812c398a9151436)

※[gRPC公式](https://www.grpc.io/docs/what-is-grpc/introduction/)より抜粋（また、以下の説明でも一部参考にしています。）



## [gRPCとは？](https://www.grpc.io/docs/what-is-grpc/faq/#what-is-grpc)



gRPCはGoogleによって開発されたRPCフレームワークです。

RPCはRemote Procedure Callの略で、逐語的に訳すと「遠隔手続呼び出し」となります。これはすなわち、「あるプログラムがネットワーク上の異なる場所に配置されたプログラムを呼び出して実行すること」と読み取れます。

#### 公式による定義
> サービス定義
多くのRPCシステムと同様に、gRPCはサービスを定義するという考えに基づいており、パラメーターと戻り値の型を使用してリモートで呼び出すことができるメソッドを指定します。デフォルトでは、gRPCはプロトコルバッファを使用しますサービスインターフェイスとペイロードメッセージの構造の両方を記述するためのインターフェイス定義言語（IDL）として。必要に応じて、他の代替手段を使用することができます。


## [gRPCとRESTの違い](https://www.grpc.io/docs/what-is-grpc/faq/#why-is-grpc-betterworse-than-rest)

WebAPIでよく使われるRESTも、離れたサーバーの機能を呼び出すという点で、RPCと同じ役割で使われるように思えます。しかし、RESTは「リソース志向を強く打ち出す」という思想的な違いがあります。リソース志向とはオブジェクト（リソース）を中心に考え、これに対してHTTPメソッドで操作していく考え方です。RPCではメソッドの呼び出しが基点になり、データはあくまでその副産物であるので考え方は逆になります。この志向性の違いから、RESTとRPCは対照的に捉えられています。



参考記事 ⇨ [REST APIの設計で消耗している感じたときのgRPC入門](https://qiita.com/disc99/items/cfca50a32240284578bb)

[Migrating APIs from REST to gRPC at WePay](https://wecode.wepay.com/posts/migrating-apis-from-rest-to-grpc-at-wepay)

[gRPC vs. REST: How Does gRPC Compare with Traditional REST APIs?](https://blog.dreamfactory.com/grpc-vs-rest-how-does-grpc-compare-with-traditional-rest-apis/)



## RESTと比較した場合のgRPCの長所

gRPCの優れた点として、大きく３つが挙げられます。

- HTTP/2による高パフォーマンス通信

- Protocol Buffersによるデータ転送

- 柔軟なライフサイクル



以下、順番に説明していきます。

※また、[スターティングgRPC (技術の泉シリーズ（NextPublishing）](https://www.amazon.co.jp/exec/obidos/ASIN/B087R87L6Z/gushernobin0f-22/)を一部参考にしております

## HTTP/2による高パフォーマンス通信

HTTP/2では通信時にデータがテキストではなくバイナリにシリアライズされて送られます。そのため小さな容量で転送でき、ネットワーク内のリソースをより効率的に使用することができます。また、HTTP/2では一つのコネクションで複数のリクエスト/レスポンスをやりとりできるので、リクエストのたびに接続と切断を行うことがない効率的な通信になります。



高速な通信ができる点において、gRPCはマイクロサービスの内部APIの通信規格として評価されています。



## Protocol Buffersによるデータ転送

![How to Use Google's Protocol Buffers in Python](https://www.freecodecamp.org/news/content/images/2020/05/unnamed-1.png)

gRPCではProtocol Buffersのフォーマットにシリアライズしてデータをやり取りします。シリアライザ自体はカスタマイズすることでJSONなどのレスポンスに変えることもできます [※公式](https://www.grpc.io/docs/what-is-grpc/faq/#can-i-use-grpc-with-my-favorite-data-format-json-protobuf-thrift-xml-)



Protocol Buffersの一番の特徴は.protoファイルというIDL(インターフェース記述言語)です。.protoファイルを書いて、コンパイラを実行すると任意の言語のサーバー/クライアント用コードを生成してくれます。わざわざ自分でAPIインターフェースを実装したり、シリアライズされたデータのエンコード/デコード処理を書く必要がありません。



このためgRPCで開発する場合はまず、スキーマを書く必要があります。gRPCではスキーマが最初に書かれるので.protoファイルを見ればAPIの使用は常に明確です。そのため、必然的にスキーマファーストの開発を行うことになります。



![スクリーンショット 2019-05-26 12.32.51.png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F221948%2Ff859843e-7b37-13a4-e680-34f8198ba111.png?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&s=46b9d29b291dde438beed2436528b260)

※[gRPC公式](https://www.grpc.io/docs/what-is-grpc/introduction/)より抜粋







## [柔軟なライフサイクル](https://www.grpc.io/docs/what-is-grpc/core-concepts/#rpc-life-cycle)

gRPCではAPIで一般的である一つのリクエストに対してレスポンスを一つ送る形式以外に、単方向、双方向のストリーミングRPCに対応しています。

![Migrating APIs from REST to gRPC at WePay](https://wecode.wepay.com/assets/2019-04-08-migrating-apis-from-rest-to-grpc-at-wepay/image4.png)

## [Unary RPC ](https://www.grpc.io/docs/what-is-grpc/core-concepts/#unary-rpc)

 クライアントから送られた一つのリクエストに対して、レスポンスを一つ返して終了する、最も一般的な形式です。

例

```golang
rpc SayHello(HelloRequest) returns (HelloResponse);
```


## [Server Streaming RPC](https://www.grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc)

 クライアントから送られたリクエストに対して、レスポンスを複数回に分けて返します。時間のかかる処理について、非同期的にレスポンスを返すことができます。

用途として、サーバーから任意のタイミングでクライアントに通知をさせたい時に使用します。具体例を挙げると、お知らせ通知やタイムラインのリアルタイム更新などです。

サーバー側の状態変化をクライアントで検知したい場合にストリーミングがないと、定期的な通信をクライアントが行う必要があり、無駄な通信が多く発生してしまいます。ServerStreamingRPCを使えば、サーバー側から状態変化時にクライアントに直接伝えることができるのでネットワーク負荷を最小にすることができます。

例

```golang
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
```


## [Client Streaming RPC](https://www.grpc.io/docs/what-is-grpc/core-concepts/#client-streaming-rpc)

クライアントからリクエストを分割してサーバーに送り、サーバーが全てリクエストを受け取ってからレスポンスを返します。大きなデータアップロードなどに用いられます。


例

```golang
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
```



## [Bidirectional Streaming RPC](https://www.grpc.io/docs/what-is-grpc/core-concepts/#bidirectional-streaming-rpc)

任意のタイミングでどちらからもリクエストレスポンスを送ることができます。ユースケースとして、チャットやオンラインゲームになります。REST APIではストリーミングが困難なのでWEBSocketサーバーを立てる必要がありましたが、gRPCの場合、単一のサーバーで双方向通信が必要なAPIとしても対応可能です。


例

```golang
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
```


## ハンズオン！(Golang)
#### サンプルリポジトリ(完成形)
https://github.com/ishishow/grpc_tutorial

#### 前提

- Goがインストールされている(Go 1.6 or higher )



#### 実践

  grpcのインストール

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

Protocol Buffers v3 をインストール。

```shell
brew install protobuf
```

Protocol Buffers の Go プラグインをインストール。

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

go mod init の実行(必要であれば)

```shell
go mod init github.com/tutorialedge/go-grpc-beginners-tutorial
```

準備完了です!

以下実装に入っていきます！！

#### 全体構成(最終)
![スクリーンショット 2021-02-24 3.39.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628777/d1771dc3-93aa-ea0b-8478-ab69fbd2a0e7.png)


------------------------------------------------------

- Server.goの作成

```go
package main

import (
	"log"
	"net"

	"google.golang.org/grpc"
)

func main() {

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

	grpcServer := grpc.NewServer()

	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %s", err)
	}
}
```

 このファイルではgrpcパッケージをインポートして、新規grpcサーバーを作成し、tcp接続を行なっています。

```go
grpcServer := grpc.NewServer()
```

[func NewServer](https://pkg.go.dev/google.golang.org/grpc#NewServer)

- Chat.protoの作成

```protobuf
syntax = "proto3";
package chat;

message Message {
  string body = 1;
}

service ChatService {
  rpc SayHello(Message) returns (Message) {}
}
```

この.protoファイルは任意の言語で記述された任意のgRPCクライアントから呼び出すことができる、単独のSayHello関数を公開しています。



- protocを使ってGo固有のgRPCコードの生成

```shell
protoc --go_out=plugins=grpc:chat chat.proto
```

これにより、chat/chat.pb.goが生成されたかと思います。もし生成されないようでしたら、chatフォルダがあるかどうか、goのパスが通っている確認してください。

また、server.goでChatServiceを登録する処理を追加します。

- ChatServiceの追加(server.go)

```go
package main

import (
	"fmt"
	"log"
	"net"

	"github.com/tutorialedge/go-grpc-beginners-tutorial/chat"
	"google.golang.org/grpc"
)

func main() {

	fmt.Println("Go gRPC Beginners Tutorial!")

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

	s := chat.Server{}

	grpcServer := grpc.NewServer()

	chat.RegisterChatServiceServer(grpcServer, &s)

	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %s", err)
	}
}
```



次に、SayHelloを取り込んでMessage本文を読み取って返すメソッドを定義します。

- Chat/chat.goの追加

```go
package chat

import (
	"log"

	"golang.org/x/net/context"
)

type Server struct {
}

func (s *Server) SayHello(ctx context.Context, in *Message) (*Message, error) {
	log.Printf("Receive message body from client: %s", in.Body)
	return &Message{Body: "Hello From the Server!"}, nil
}

```



ここまで終わったらサーバーを実行してみます。

```shell
go run server.go
Go gRPC Beginners Tutorial!
```

gRPCサーバーが起動しました！



### GoでのgRPCクライアントの構築

サーバーが稼働しているので、サーバーと通信するシンプルなクライアントを構築しましょう。

- Client.go

```go
package main

import (
	"log"

	"golang.org/x/net/context"
	"google.golang.org/grpc"

	"github.com/tutorialedge/go-grpc-beginners-tutorial/chat"
)

func main() {

	var conn *grpc.ClientConn
	conn, err := grpc.Dial(":9000", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %s", err)
	}
	defer conn.Close()

	c := chat.NewChatServiceClient(conn)

	response, err := c.SayHello(context.Background(), &chat.Message{Body: "Hello From Client!"})
	if err != nil {
		log.Fatalf("Error when calling SayHello: %s", err)
	}
	log.Printf("Response from server: %s", response.Body)

}
```



このプログラムを実行すると、クライアントがサーバーからメッセージを受け取ることができます。

```shell
go run client.go
2020/04/30 20:10:09 Response from server: Hello From the Server!
```



## 最後に

ここまででGo言語で単純なgRPCクライアントとサーバーを構築する方法について説明してきました。クライアントから着信メッセージを受け入れて、それらのクライアントに応答を返す処理です。

まだこの記事だけではgRPCの一部分しか触れていないので共に勉強していきましょう！
不明点などございましたらお気軽コメントお願いしますmm




### 参考文献

[マイクロサービスバックエンドAPIのためのRESTとgRPC](https://www.slideshare.net/disc99_/apirestgrpc)

https://blog.cloudflare.com/road-to-grpc/

https://wecode.wepay.com/posts/migrating-apis-from-rest-to-grpc-at-wepay

[いまさらだけどgRPCに入門したので分かりやすくまとめてみた](https://qiita.com/gold-kou/items/a1cc2be6045723e242eb)

[gRPCって何？
](https://qiita.com/oohira/items/63b5ccb2bf1a913659d6)