GoでgRPC API超入門

自分の中で少しずつ理解できてきたので、小分けにして整理していきたくこの記事を書きました。
同じ初学者のお役に立てれば。

::: note
当記事は実装メインで解説します。
protoって何の略？これは内部でどうなってるの？的な細かい話は省いています。
気になるところがあったら他記事で補完してください🙏
:::

## 構成
今回は最小限の超簡単な構成とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/f317ee38-fcdc-4807-dd65-089a1af1f128.png)
初学者はHello,World!を出力しなければならないという法律に則り、Hello,World!を出力するAPIを実装していきます。

実装では、クライアントのインスタンス、gRPCサーバのインスタンスをそれぞれ立ち上げ、リクエスト〜レスポンスまでの流れを確認する、ということをやります。

## 大まかな手順
ゆるふわ概念図を記しておきます。
### 全体像
まずは作成するものの全体像。さっきのクライアント↔︎gRPCサーバの図をもうちょい細かく︎書いた図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/280ed826-af53-5ed8-5941-825c96f19e56.png)
※クライアント（インスタンス）、gRPCサーバ（インスタンス）はgoファイルをコマンド実行している間だけ生きています。一方、pb.goファイルは一度作成されたらずっとそこにあります。

### ざっくり手順　〜３ステップ〜

1. protoファイルというものを作って、コマンドを実行します。このコマンドでprotoファイルを元としたファイルが生成されます。そのあと、サーバ、クライアント側の実装をします。
![grpc go ファイル作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/69d248c5-f959-7052-8e56-9fafaad1b1e6.gif)

2. 次に、コマンド実行でgRPCサーバを作成します。
![grpc go ファイル作成2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/36cddaec-ec85-5864-1a1c-84675adac3bf.gif)

3. 最後に、クライアントのインスタンスを生成し、gRPC APIを叩けば完成！
![grpc go ファイル作成3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/f4f3e0dd-343f-3044-e6d4-46b9e3ba1053.gif)


上の図より、初手で作るファイルは以下の３つになります。
- server.go // サーバの実装
- client.go // クライアント側の実装
- hello.proto // APIの細かいリクエスト・レスポンス内容の実装

図でみると、↓の赤枠のところですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/5b8afc14-edbd-634a-ae46-7f77c65921a2.png)

## フォルダ構成
１つのフォルダ直下に全てをぶっ込みます。フォルダ構成が簡単だとテンション上がるので。
```md
grpc-test/
┝ server.go
┝ client.go
└ hello.proto

```

## いざ実装！の前に

GitHubに空のリポジトリ「grpc-test」を作っておき、
このリポジトリをローカルに持ってきた上で、
「grpc-test」直下に諸々のファイルを作っていく
という流れをやっておきます。
（pb.goファイルのURL指定のため）

## hello.protoの実装
まずは第一にこれを実装します。なぜならこのprotoファイルにリクエスト・レスポンスの全てをぶち詰め込むからです。goでのgrpcはこの書き方が一般的です。
```proto
syntax = "proto3";

package hello;

// このファイルにより生成されるpb.goファイルを置く（出力する）ディレクトリ
option go_package = "/pb";


service HelloService {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

```
コード中
`option go_package = "/pb";`
の部分は、pb.goファイルが生成される場所になります。
今回、上記のように指定したことで、grpc-test直下にpbファイルが作られることが確定しました。他のパスに置きたければここを編集すればOK。

次の項「pb.goファイルの生成」を実行したあとは、こんな配置になるはず。
```md
grpc-test/
┝ pb/
|  ┝ hello.pb.go
|  └ hello_grpc.pb.go
(略)
```

## pb.goファイルの生成

1. ターミナルを開き、今回のルート「grpc-test」フォルダ直下に移動します。
1. 以下のコマンドを実行します。
    ```sh
    protoc --go_out=. --go-grpc_out=. hello.proto
    ```
1. 「pb」フォルダが作成され、直下に「hello.pb.go」「hello_grpc.pb.go」が作成されています。

現時点でのフォルダ構成は以下のようになっていると思います。

```md
grpc-test/
┝ pb/
|  ┝ hello.pb.go
|  └ hello_grpc.pb.go
┝ server.go
┝ client.go
└ hello.proto
```

protoファイル作成、pb.goファイル作成が完了しました。
ここで先ほどの図を振り返ってみます。今回やったのは↓の部分にあたります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/42f3428e-e3ba-68dd-e8e1-facf5109186d.png)


## server.go の実装
```go server.go
package main

import (
  "context"
  "log"
  "net"

  "google.golang.org/grpc"
  pb "github.com/<your_user_name>/grpc-test/pb" // 後ほど生成されたhello.pb.goとhello_grpc.pb.goが置いてあるパスに変更
)

type server struct {
  pb.UnimplementedHelloServiceServer
}

// このSayHelloメソッドがサービスハンドラーの役割を果たす
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
  lis, err := net.Listen("tcp", ":50051") // リスナー作成
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer() // gRPCサーバーのインスタンスを作成
  pb.RegisterHelloServiceServer(s, &server{}) // HelloServiceサービスをサーバーに登録
  log.Printf("server listening at %v", lis.Addr())
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}
```

先ほどの図では↓の部分にあたります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/deb7a0b8-1ef0-a497-ce09-ebd04c00e3a4.png)

ここで 
```sh
go mod init
go mod tidy
```
コマンドを実行して、`go.mod`、`go.sum` 兄弟をつくっておきます。


`go run server.go`コマンドを打つとgRPCサーバがつくられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/aaac9e61-f46f-f035-304b-3f0a44e994b5.png)
start listening at <port番号>で返されればOK。ctrl+Cで抜けれます。

:::note info
（別に読まなくていい補足）
RPCではHTTPルーティングのようなURLベースのルーティングはなく、
protoファイルで定義されたサービスとメソッドがルーティングの役割を果たします。
今回は、SayHelloメソッドがHelloServiceサービスにルーティングされるようになります。
:::

## client.goの実装
```go client.go
package main

import (
  "context"
  "log"
  "os"
  "time"

  "google.golang.org/grpc"
  pb "xxx" // 生成されたhello.pb.goとhello_grpc.pb.goのパッケージパスに変更
)

func main() {
  conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()
  c := pb.NewHelloServiceClient(conn)

  name := "world"
  if len(os.Args) > 1 {
    name = os.Args[1]
  }

  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()

  r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
  if err != nil {
    log.Fatalf("could not greet: %v", err)
  }
  log.Printf("Greeting: %s", r.GetMessage())
}
```

ここで 
```sh
go mod tidy
```
コマンドを実行して、`go.mod`、`go.sum` を更新しておきます。

先ほどの図では↓の部分にあたります。
リクエスト送信、レスポンス受け取り＆表示までが、クライアント側で実装した内容になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/7d333dd0-af2b-5fe1-7b9e-87c4ae9b75bf.png)

:::note warn

main関数 `func main()` が同じ階層に２つ以上存在する場合、警告が出るっぽいです。
mainの実行自体はできるので、今回は警告を無視して進めます。

:::

:::note

（別に読まなくていい補足）
この実装したクライアントは、リモートプロシージャコール（RPC）という通信方法でリクエストを送信します。
先ほど作成したserver.goのサーバも、リモートプロシージャコール（RPC）という通信を受け取り、処理します。

:::

## クライアントからリクエストをgRPCサーバに投げてみる

ここまでの実装で、やっと完成です。
フォルダはこんな感じになってるはず。
```md
grpc-test/
┝ pb/
|  ┝ hello.pb.go
|  └ hello_grpc.pb.go
┝ go.mod
┝ go.sum
┝ server.go
┝ client.go
└ hello.proto
```
それでは、早速やってみます。

順序はこれだけ。
1. gRPCサーバを立ち上げる
1. クライアントからリクエストを投げる

### 1. gRPCサーバを立ち上げる
「grpc-test」リポジトリ直下に行きます。
ターミナルを立ち上げ、以下のコマンドを実行します。
```sh
go run server.go
```
これで `start listening at <port番号>`のような形で返されればOKです。
ctrl+Cで抜けれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/aaac9e61-f46f-f035-304b-3f0a44e994b5.png)

### 2. クライアントからリクエストを投げる
「grpc-test」リポジトリ直下に行きます。
先ほどサーバを起動したターミナルとは別のターミナルを立ち上げ、以下のコマンドを実行します。
```sh
go run client.go
```
これで `Greeting: Hello world`と返されました！

ちなみに、
```sh
go run client.go hoge
```
を実行すると、`Greeting: Hello hoge`と返されます。

## gRPC APIのつくりかた まとめ
protoファイルを作ってpb.goを生成する。そのあと、サーバ、クライアント側の実装をする。
![grpc go ファイル作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/69d248c5-f959-7052-8e56-9fafaad1b1e6.gif)

これでファイルは完成。

次に、`go run server.go` コマンドでgRPCサーバを作成。
![grpc go ファイル作成2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/36cddaec-ec85-5864-1a1c-84675adac3bf.gif)

最後に、クライアントのインスタンスを生成し、gRPC APIを叩く。
すると、以下のようなルートを通り、レスポンスが返されます。
![grpc go ファイル作成3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821214/f4f3e0dd-343f-3044-e6d4-46b9e3ba1053.gif)

リクエスト、レスポンスの中身はgRPCサーバ内でいろいろと加工されたりします。

厳密には違う部分もあるかもですが、ざっくりこんな感じです。
ご指摘あれば何なりと！