Dockerで始めるgRPC

# はじめに

dockerを使ったgrpcをまとめたいと思います。
内容は、クイックスタートに準拠しています。

https://grpc.io/docs/languages/go/quickstart/

レポジトリーを作りました。

https://github.com/sugiyama404/practice_grpc

## gRPCとは

gRPCとは、http2とバイナリを使用した高速通信可能なgoogleが開発したPRCフレームワークです。

![Qiita](https://grpc.io/img/landing-2.svg)

## プロトコルバッファとは

構造化データ定義し、シリアライズするためのオープンソースメカニズムです。
protocコマンドを打つことで、goファイルを自動生成します。

# コーディング
## Dockerをbuild

```console:ファイル構成
.
├── client
│   ├── Dockerfile
│   └── go_grpc
│       └── bin
│           └── go_starter
├── docker-compose.yml
└── server
    ├── Dockerfile
    └── go_grpc
        └── bin
            └── go_starter
```

```dockerfile:Dockerfile
FROM golang:1.19-buster

ENV PROTOBUF_VERSION 3.17.3

RUN apt-get update \
    && apt-get install -y protobuf-compiler unzip --no-install-recommends \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /practice-grpc
COPY . /practice-grpc

## build時のみ使いたいので、runで実行。
RUN chmod +x go_grpc/bin/go_starter && go_grpc/bin/go_starter

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


```yaml:docker-compose.yml
version: '3.7'

services:
  server:
    build: ./server
    container_name: "server-grpc"
    volumes:
      - ./server:/practice-grpc
    ports:
      - "8080:8080"
    tty: true
  client:
    build: ./client
    container_name: "client-grpc"
    volumes:
      - ./client:/practice-grpc
    tty: true

```

go mod initコマンドも合わせて行っています。

```bash:go_grpc/bin/go_starter
#!/usr/bin/env bash

set -euo pipefail

function exits() {
  local cwd="$1"
  local File="${cwd}/go.mod"
  local c_dir=${cwd##*/}

  if [ -f "${File}" ]; then
    echo "${File} exist"
  else
    cd "${c_dir}"
    go mod init "${c_dir}"
  fi
}

function main() {
  local cwd
  cwd="$(cd "$(dirname "$0")/.." && pwd)"
  exits "${cwd}"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  main "$@"
fi
```

buildします。

```console
docker compose up
```

ここまでで、初期設定は終わりです。

## protoコマンドを実行
ディレクトリーとファイルを作ります。

```console
mkdir server/go_grpc/pb server/go_grpc/proto
touch server/go_grpc/proto/route_guide.proto
```


```proto:server/go_grpc/proto/Helloworld.proto
syntax = "proto3";

package helloworld;

option go_package = "./pb"; // 自動生成されるgoファイルの出力先を指定します。

// サービスメソッドで使用されるすべてのリクエスト
// およびレスポンスタイプのプロトコルバッファメッセージタイプ定義

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

```

```console
protoc -I. --go_out=. --go-grpc_out=. proto/*.proto
```
-Iは起点になっているディレクトリを表しています。.はカレントディレクトリ。
--go_outはgoファイルに変換することを意味しています。

pb/helloworld.pb.go
pb/helloworld_grpc.pb.go
の二つができました。

同じことをclient側でもします。


## server側

server側もclient側も見やすくするために書き直しました。

```golang:server/main.go
package main

import (
	"context"
	"log"
	"net"

	pb "go_grpc/pb"
	"google.golang.org/grpc"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
	lis, err := net.Listen("tcp", "server:8080")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```

## client側
```golang:client/main.go
package main

import (
	"context"
	"log"
	"time"

	pb "go_grpc/pb"

	grpc "google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	conn, err := grpc.Dial("server:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "yuki"})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}
```

# 通信

準備が整ったので、実際に通信をしてみます。


```console:最終的なファイル構成
.
├── client
│   ├── Dockerfile
│   └── go_grpc
│       ├── bin
│       │   └── go_starter
│       ├── client
│       │   └── main.go
│       ├── go.mod
│       ├── go.sum
│       ├── pb
│       │   ├── Helloworld.pb.go
│       │   └── Helloworld_grpc.pb.go
│       └── proto
│           └── Helloworld.proto
├── docker-compose.yml
└── server
    ├── Dockerfile
    └── go_grpc
        ├── bin
        │   └── go_starter
        ├── go.mod
        ├── go.sum
        ├── pb
        │   ├── Helloworld.pb.go
        │   └── Helloworld_grpc.pb.go
        ├── proto
        │   └── Helloworld.proto
        └── server
            └── main.go
```

server-grpcコンテナ内に入り以下のコマンドを実行します。

```console
cd go_grpc
go run server/main.go
```
```console:server
2022/12/31 05:25:49 server listening at 172.18.0.3:8080
2022/12/31 05:25:55 Received: yuki
```
client-grpcコンテナ内に入り以下のコマンドを実行します。

```console
cd go_grpc
go run client/main.go
```
```console:client
2022/12/31 05:25:58 Greeting: Hello yuki
```

期待通りの動きをしています。