これから始めるgRPC

どーも、フリーランスエンジニアのyutaです。

今回gRPCについて自分への備忘としてまとめます。

gRPCとは

gRPCとはGoogleが開発したRPCフレームワークのことです。(RPCとはRemote Procedure Callの略で、遠隔にある環境に接続して関数を呼び出すイメージだそうです。)

「Protocol Buffers」(protobuf)を使いデータをシリアライズし高速で低容量で、様々な言語でアプリケーション間の通信に利用できます。

インストール方法(準備編)

Macではhomebrewでprotobufをインストールできます。

$ brew install protobuf

もしまだhomebrewをインストールしてない人は下記を参考にインストールしてください。

実際に試してみる

今回はGolangとRubyで実際に試してみます。

単一ディレクトリで作りますが、構成はこんな感じです。

$ tree
.
├── README.md
├── golang
│   ├── client.go
│   ├── go.mod
│   ├── go.sum
│   ├── hello_world.go
│   └── pb
│       └── hello_world.pb.go
├── hello_world.proto
└── ruby
    ├── hello_client.rb
    └── pb
        ├── hello_world_pb.rb
        └── hello_world_services_pb.rb

Githubにサンプルリポジトリも作ったので良かったら使ってみてください。

https://github.com/cv0629-blog/grpc_go_ruby_tutorial

protoを定義

サーバー側となるGolangから進めていきます。

まずGolangでgRPCを使うためにパッケージをインストールします。

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

次に下記のようなprotoファイルを定義します。この一つのprotoを使い、GolangとRubyで共通のインターフェースを生成していきます。

syntax = "proto3";

option go_package = ".;hello";

// HelloRequest を受け取って HelloResponse を返すメソッドの定義
service Greeter {
  rpc Hello (HelloRequest) returns (HelloResponse);
}

// リクエストの定義
message HelloRequest {
  string name = 1;
}

// レスポンスの定義
message HelloResponse {
  string message = 1;
}

protoファイルを定義したら、まずはGolang用でコンパイルするために下記を実行します。

$ protoc -I ./ hello_world.proto --go_out=plugins=grpc:./golang/pb

そうすると、golang/pbディレクトリにhello_world.pb.goが作られると思います。中身は結構長く作られるので今回は省略します。

生成されたインターフェースを実装したサーバを構築

今回は下記のようにサーバー側を実装しました。

package main

import (
	"context"
	"log"
	"net"

	"greet/pb" // ローカルをパッケージ化しているので、必要に応じてgo mod init greet を実行してください。
	"google.golang.org/grpc"
)

func main() {
	listener, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("failed to listen: %v\n", err)
		return
	}
	server := grpc.NewServer()
	hello.RegisterGreeterServer(server, &Service{})
	log.Printf("Greeter service is running!")
	server.Serve(listener)
}

type Service struct{}

func (*Service) Hello(ctx context.Context, req *hello.HelloRequest) (*hello.HelloResponse, error) {
	log.Printf("Helloが呼び出されました!")

	return &hello.HelloResponse{
		Message: "Hello, " + req.Name,
	}, nil
}

実行するとこんな感じで起動します。

$ go run hello_world.go 
2020/10/12 23:36:13 Greeter service is running!

起動のときに許可を求められたら許可してください。

Ruby側の定義(リクエスト側)

まずはRubyでgRPCを扱うためのライブラリをインストールします。

$ gem install grpc
$ gem install grpc-tools

インストールできたらhello_world.protoを下記コマンドでコンパイルします。

$ grpc_tools_ruby_protoc -I ./ hello_world.proto --ruby_out=ruby/pb --grpc_out=ruby/pb

そうすると、hello_world_pb.rbhello_world_services_pb.rbファイルが生成されます。

それらを使って実装したリクエストのコードです。

this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'pb')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)

require 'grpc'
require 'hello_world_services_pb'

def main
  stub = Greeter::Stub.new('localhost:8080', :this_channel_is_insecure)

  begin
    message = stub.hello(HelloRequest.new(name: 'Yuta Nakamura'))
    puts message.to_json

  rescue GRPC::BadStatus => e
    abort "ERROR: #{e.message}"
  end
end

main

これで実行すると下記のように出力されます。

$ ruby hello_client.rb 
{"message":"Hello, Yuta Nakamura"}

使い所

protoファイルを共有することで、言語が違くてもインターフェースが共通で定義できため、かなり実装コストやチーム開発でのずれが少なくなると思います。

今度はTypeScriptなど、フロントとも共有できないか試してみたいです。

参考:
https://www.grpc.io/docs/what-is-grpc/
https://tech.smartcamp.co.jp/entry/2019/03/28/175137
https://qiita.com/kwashi/items/533bbb7d09e723c8b56f