14
6

More than 5 years have passed since last update.

はじめに

この記事はユニークビジョン株式会社 Advent Calendar 2018の11日目の記事です。

コンテナを中心とした開発が主流になっていくなかで、コンテナ間の通信はどうするのかという問題がつきまといます。
普通にAPIとして実装しても良いと思いますが、Googleが開発しているgRPCを活用すれば通信方法やAPIのドキュメントの準備の手間を軽減したり、HTTP/2を利用した良好なパフォーマンスなど様々なメリットを享受できます。

社内への宣伝を兼ねて、今回gRPCのチュートリアルをやってみたいと思います。

gRPC

Google社が開発しているRPCフレームワークです。.protoを定義することで、そこから様々な言語のコードを生成し、サーバにメソッドを定義することで、異なる言語間で通信が可能となります。
公式のドキュメントに対応言語のチュートリアルなどがまとまっており、今回はここ1を参考にしつつ実装していきます。

Hello, World

準備

 環境

  • Go(今回は1.10を使用します)
    • Goのセットアップに関してはGo公式のドキュメントやその他の記事を参照してください
    • $GOPATH定義までしてあるといいとおもいます

ライブラリ

gRPCと併せて今回goで実装するのでGoPluginも

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

ディレクトリ構成

適当なディレクトリ($GOPATH以下が望ましい)に以下のような形で準備します。

.
├── client
│   └── main.go
├── hellogrpc
│   └── hellogrpc.proto
└── server
    └── main.go

.protoの定義

今回はhellogrpcディレクトリ以下に、サーバ - クライアント間のインタフェースを定義するprotoファイルを設置します。

このprotoファイルを元にgRPCがサポートしている各言語の実装を生成します。

hellogrpc.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.tokikokoko.hellogrpc.server";
option java_outer_classname = "HelloGrpc";

package HelloGrpc;

// The greeting service definition.
service HelloGrpc {
    // Sends a greeting
    rpc GreetServer (GreetRequest) returns (GreetMessage) {}
}

message GreetRequest {
    string name = 1;
}

message GreetMessage {
    string msg = 1;
}

上記のファイルで注目してほしいのはpackage HelloGrpc;以下で、

// The greeting service definition.
service HelloGrpc {
    // Sends a greeting
    rpc GreetServer (GreetRequest) returns (GreetMessage) {}
}

service内で今回実装するgRPCサーバにどのようなメソッドを実装するのかを定義します。

rpcに続く文がserviceのメソッドとなり、ここではGreetServerというメソッドを定義しています。これはGreetRequest型のmessageを引数としてGreetMessage型のmessageを返すという定義です。

message GreetRequest {
    string name = 1;
}

message GreetMessage {
    string msg = 1;
}

messageに続く文でメッセージ型を定義しており、構造体のように扱えます。

ここでは

  • string型のnameを持ったGreetRequest
  • string型のmsgを持ったGreetMessage

を定義しています。

.protoをコンパイル

定義した.protoファイルはコンパイルすることで、各言語の実装を出力します。出力されたコードをimportすることで定義したインタフェースを実装、呼び出すことができます。

今回定義したprotoファイルで、Go言語のコードを出力するためには、

$ protoc -I hellogrpc/ hellogrpc/hellogrpc.proto --go_out=plugins=grpc:hellogrpc

を実行することで、hellogrpc/hellogrpc.pb.goが出力されます。

サーバとクライアントを実装する

出力されたコードをimportして、実際にサーバとクライアントを実装していきます。

server/main.go
package main

import (
    "context"
    "fmt"
    "net"

    "google.golang.org/grpc"
    "log"

    // .protoから生成されたコードをimportしている
    // 今回は筆者の$GOPATH内に作成したので適宜プロジェクトを作成したパスに合わせる
    pb "github.com/tokikokoko/hello_grpc/hellogrpc"
)

// gRPC server struct
type server struct {
}

// .protoで定義したGreetServerを定義している
func (s *server) GreetServer(ctx context.Context, p *pb.GreetRequest) (*pb.GreetMessage, error) {
    log.Printf("Request from: %s", p.Name)
    return &pb.GreetMessage{Msg: fmt.Sprintf("Hello, %s. ", p.Name)}, nil
}

func main() {
    // gRPC
    port := 10000
    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    log.Printf("Run server port: %d", port)
    grpcServer := grpc.NewServer()
    // メソッドを定義
    pb.RegisterHelloGrpcServer(grpcServer, &server{})
    // gRPCサーバを公開
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
client/main.go
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    // .protoから生成されたコードをimportしている
    // 今回は筆者の$GOPATH内に作成したので適宜プロジェクトを作成したパスに合わせる
    pb "github.com/tokikokoko/hello_grpc/hellogrpc"
)

const (
    address = "localhost:10000"
)

func main() {
    // gRPCサーバへのconnectionを作成
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    // connection終了処理
    // 関数終了後に実行される
    defer conn.Close()

    c := pb.NewHelloGrpcClient(conn)

    name := "ほげ太郎"

    // タイムアウトを設定
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    // .protoで定義したサーバのGreetServerを呼び出している
    r, err := c.GreetServer(ctx, &pb.GreetRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Response: %s", r.Msg)
}

実行結果

サーバ側
$ go run server/main.go
2018/12/11 00:37:24 Run server port: 10000
2018/12/11 00:37:26 Request from: ほげ太郎
クライアント側
$ go run client/main.go
2018/12/11 00:37:26 Response: Hello, ほげ太郎.

まとめ

超簡単なgRPCサーバ&クライアントを実装しました。gRPCの概要を理解してもらえれば幸いです。超ぬるい記事ですが、初投稿なのでお許しくださいorz

明日も私が担当です。CI/CDするとか言っていましたがgRPCの導入の話が存外に長くなってしまったので、今日に引き続きgRPCのstreamを使った簡素なチャットツールを実装しようと思います。

14
6
1

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
14
6