LoginSignup
2
2

More than 1 year has passed since last update.

GolangでgRPCに入門

Last updated at Posted at 2021-08-16

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

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

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

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

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

プロジェクトの用意

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

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/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

参考

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)

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2