GolangでgRPCに入門



golangでgRPCを使ったプロトタイプを作る手順です。

https://qiita.com/marnie_ms4/items/4582a1a0db363fe246f3#grpc%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

を記事を多いに参考にしてます。
この記事のコードは上記の記事と同一です。どこにコードを書くかと、`protoc`コマンドについて多少明確になっています。

Goのバージョン管理には`gvm`を使っています。
Goのバージョンは `go version go1.16.5 darwin/amd64` です。

`protoc`コマンドを使うので以下でinstallしてください。

http://google.github.io/proto-lens/installing-protoc.html

gRPCの何がいいかについては


https://speakerdeck.com/line_developers/starting-grpc?slide=7


# プロジェクトの用意

適当なプロジェクトをこしらえます。

```
go mod init example.com/cat
```

必要なパッケージを入れます。

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

こんな感じの`go.mod`ができます。

```
module example.com/cat

go 1.16

require (
	github.com/golang/protobuf v1.5.2 // indirect
	golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
	golang.org/x/sys v0.0.0-20210816032535-30e4713e60e3 // indirect
	golang.org/x/text v0.3.7 // indirect
	google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
	google.golang.org/grpc v1.40.0 // indirect
	google.golang.org/protobuf v1.27.1 // indirect
)

```


# ディレクトリを作る

以下のようにフォルダとファイルを作成します。

```
client/main.go
proto/cat.proto
server/main.go
service/main.go
```

このうちまず`proto/cat.proto`を以下のように書きます。

```proto:proto/cat.proto
syntax = "proto3";

option go_package = "./;pb";

package cat;

service Cat {
    rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {}
}

message GetMyCatMessage {
    string target_cat = 1;
}

message MyCatResponse {
    string name = 1;
    string kind = 2;
}
```

`option go_package`ですが、packageのimport pathを`xx.proto`ファイルに与える必要があって、`protoc`コマンド実行字か、`.proto`に記述するのがするかできます。推奨としては`xx.proto`ファイルに書くのがいいようです。
https://developers.google.com/protocol-buffers/docs/reference/go-generated#package

関連
- https://github.com/techschool/pcbook-go/issues/3#issuecomment-824040393
- https://blog.ebiiim.com/posts/grpc-with-go-mod/

# コードの自動生成

この`proto/cat.proto`に基づいて、`protoc`コマンドで、gRPCのコードを自動生成します。

```
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/cat.prot
```

参考 - https://grpc.io/docs/languages/go/basics/#generating-client-and-server-code

上記のコマンドで

- `proto/cat_grpc.pb.go`
- `proto/cat.pb.go`

が生成されますが、`proto/cat_grpc.pb.go`の中は以下のように`mustEmbedUnimplementedCatServer()`というメソッドも生成されます。


```proto/cat_grpc.pb.go
type CatServer interface {
	GetMyCat(context.Context, *GetMyCatMessage) (*MyCatResponse, error)
	mustEmbedUnimplementedCatServer()
}
```

このメソッドがあると今回参考にした実装が動かなかったので、上記のメソッドが生成されないように自動生成コマンドを少し変更します。


```
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=require_unimplemented_servers=false:. --go-grpc_opt=paths=source_relative proto/cat.proto
```

### 参考


- https://github.com/grpc/grpc-go/issues/3794#issuecomment-725860916
- https://sat8bit.github.io/logs/daily-2021-01-28/

# Service

```service/main.go
package service

import (
	"context"
	"errors"

	pb "example.com/cat/proto"
)

type MyCatService struct {
}

func (s *MyCatService) GetMyCat(ctx context.Context, message *pb.GetMyCatMessage) (*pb.MyCatResponse, error) {
    switch message.TargetCat {
    case "tama":
        //たまはメインクーン
        return &pb.MyCatResponse{
            Name: "tama",
            Kind: "mainecoon",
        }, nil
    case "mike":
        //ミケはノルウェージャンフォレストキャット
        return &pb.MyCatResponse{
            Name: "mike",
            Kind: "Norwegian Forest Cat",
        }, nil
    }
return nil, errors.New("Not Found YourCat")
}

func (s *MyCatService) UnimplementedGreeterServer() {}
```

# Client

```client/main.go
package main

import (
	"context"
	"fmt"
	"log"

	pb "example.com/cat/proto"

	"google.golang.org/grpc"
)
func main() {
    //sampleなのでwithInsecure
    conn, err := grpc.Dial("127.0.0.1:19003", grpc.WithInsecure())
    if err != nil {
        log.Fatal("client connection error:", err)
    }
    defer conn.Close()
    client := pb.NewCatClient(conn)
    message := &pb.GetMyCatMessage{TargetCat: "tama"}
    res, err := client.GetMyCat(context.TODO(), message)
    fmt.Printf("result:%#v \n", res)
	fmt.Println("---------")
    fmt.Printf("error::%#v \n", err)
}
```

# Server

```server/main.go
package main

import (
	"log"
	"net"

	"example.com/cat/service"

	pb "example.com/cat/proto"

	"google.golang.org/grpc"
)
func main() {
    listenPort, err := net.Listen("tcp", ":19003")
    if err != nil {
        log.Fatalln(err)
    }
    server := grpc.NewServer()
    catService := &service.MyCatService{}
    // 実行したい実処理をseverに登録する
    pb.RegisterCatServer(server, catService)
    server.Serve(listenPort)
}
```

# 実行してみる

ターミナルで二つのプロセスを使います。

まず以下をうちこみます。

```
go run server/main.go 
```

次に別のプロセスでサーバにアクセスします

```
go run client/main.go 
```

`client/main.go`で以下のoutputが見れます。

```
result:&pb.MyCatResponse{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0000e5a08)}, sizeCache:0, unknownFields:[]uint8(nil), Name:"tama", Kind:"mainecoon"} 
---------
error::<nil> 
```

`tama`だけ取得したければ、以下のように変更します。

```client/main.go
fmt.Printf("result:%#v \n", res.Name)
```


# 参考

- https://note.com/dd_techblog/n/nb8b925d21118
- https://tech.smartcamp.co.jp/entry/grpc-and-protobuf-servey
- https://developers.google.com/protocol-buffers/docs/proto3#unknowns