【gRPC】GoでUnary APIを実装する

#はじめに
前の記事で紹介したgRPCのAPIタイプの内、1リクエストに対して1レスポンスを返すUnaryを実装してみました。

https://qiita.com/suzuki0430/items/2be2d532cedd200937ca#%E6%A7%98%E3%80%85%E3%81%AAapi%E3%81%AE%E3%82%BF%E3%82%A4%E3%83%97

#実装
細かい手順に分けて実装していきます。

##準備
GoでgRPCやProtocol Bufferを実装するにあたり、以下のライブラリを導入します。

```
$ go get -u google.golang.org/grpc
$ go get -u github.com/golang/protobuf/protoc-gen-go
```
また、シェルにパスを通します(今回は`.bash_profile`)。

```bash
export GO_PATH=~/go
export PATH=$PATH:/$GO_PATH/bin
```

https://github.com/grpc/grpc-go

https://github.com/golang/protobuf

##スキーマ作成(.protoファイルの作成)
`greet.proto`というファイルを作成し、スキーマを定義します。
送信するメッセージの型を`Greeting`で定義し、Unaryによるリクエストとレスポンスの型を`GreetRequest`と`GreetResponse`で定義します。
また、APIのタイプ(どのようなリクエストとレスポンスの形式か)をサービス`GreetService`で定義します。

```proto
syntax = "proto3";

package greet;
option go_package="./greet/greetpb";

message Greeting {
    string first_name = 1; // タグ番号
    string last_name = 2;
}

message GreetRequest {
    Greeting greeting = 1;
}

message GreetResponse {
    string result = 1;
}

service GreetService{
    // Unary
    rpc Greet(GreetRequest) returns (GreetResponse) {};
}
```

##コード生成(.pb.goファイルの作成)
以下のコマンドにより、作成したスキーマからコード(Protocol Buffer)を生成します。

```
protoc greet/greetpb/greet.proto --go_out=plugins=grpc:.
```
するとスキーマで定義した`Greeting`の構造体の型やそのレシーバ(メソッド)などがGoのコードとして生成されます(以下が生成コードの一部)。
これらのメソッドをクライアントとサーバで使用することで、gRPCによる通信を実現することができます。

```go
type Greeting struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	FirstName string `protobuf:"bytes,1,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"`
	LastName  string `protobuf:"bytes,2,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"`
}

func (x *Greeting) GetFirstName() string {
	if x != nil {
		return x.FirstName
	}
	return ""
}

func (x *Greeting) GetLastName() string {
	if x != nil {
		return x.LastName
	}
	return ""
}
```

##サーバ実装
生成したAPIをサーバに実装(フック)します。
server構造体を用意して、Protocol Buffer(`greet.pb.go`)で定義された`GreetServiceServer`と同じ型でメソッド(レシーバ)を埋め込みます。

レシーバ`Greet()`では、リクエストで取得した`firstName`を`Hello [firstName]`というレスポンスで返すような処理を行います。
`Greet()`内の`GetGreeting()`などもProtocol Bufferで定義されたものを用います。

```go:server.go
package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"github.com/suzuki0430/grpc-project/greet/greetpb"

	"google.golang.org/grpc"
)

type server struct{}

func (*server) Greet(ctx context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) {
	fmt.Printf("Greet function was invoked with %v\n", req)
	firstName := req.GetGreeting().GetFirstName()
	result := "Hello " + firstName
	res := &greetpb.GreetResponse{
		Result: result,
	}
	return res, nil
}

func main() {
	fmt.Println("Hello world")

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

	s := grpc.NewServer()
	greetpb.RegisterGreetServiceServer(s, &server{})

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```
サーバをlistenする処理は`main()`内に記述します。
サーバの立ち上げには以下のコマンドを利用します。

```
go run greet/greet_server/server.go
```

##クライアント実装
Protocol Buffer(`greet.pb.go`)に定義されたメソッドを用いてリクエストの作成・送信とレスポンスの取得を行う処理を`doUnary()`にまとめます。
`server.go`に記述したメソッド`Greet()`をクライアント側で呼び出しているように、この部分でRPC(クライアントでサーバのコードを実行する)を行っています。

```go:client.go
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"google.golang.org/grpc/codes"

	"github.com/suzuki0430/grpc-project/greet/greetpb"

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

func main() {
	fmt.Println("Hello I'm a client")
	cc, err := grpc.Dial("localhost:50051", grpc.WithInsecure)
	if err != nil {
		log.Fatalf("could not connect: %v", err)
	}
	defer cc.Close()

	c := greetpb.NewGreetServiceClient(cc)

	doUnary(c)
}

func doUnary(c greetpb.GreetServiceClient) {
	fmt.Println("Starting to do a Unary RPC...")
	req := &greetpb.GreetRequest{
		Greeting: &greetpb.Greeting{
			FirstName: "Katsumi",
			LastName:  "Yamada",
		},
	}
	res, err := c.Greet(context.Background(), req)
	if err != nil {
		log.Fatalf("error while calling Greet RPC: %v", err)
	}
	log.Printf("Response from Greet: %v", res.Result)
}
```

#参考資料

https://www.udemy.com/share/101Zo03@ZVlM73FMKz9hMnVOMzhGeMFDKKhfZFWCB0Fy4sGO-tBCdQeK607U-33rpGYvRiTh/

https://github.com/techschool/pcbook-go/issues/3#issuecomment-821860413

https://qiita.com/TsuyoshiUshio@github/items/34b63b663ffd56125c07