まえがき
この記事は以下の順序で組み立てられています。
- gRPC用のコードを自動生成する
- APIとして呼び出される関数の実装をする
- gRPCサーバーとして起動する
- CLIツールを使って呼び出してみる
記事を書くために動作確認をしている環境はUbuntu18.04
です。
ただしMacやその他のOSでも、ツールのインストール周りを公式のドキュメントに差し替えて頂ければ動かせるかと思います。
間違いなどありましたら、コメントや編集リクエストなどを貰えると嬉しいです。
gRPC用のコードを自動生成する
1. リクエストとレスポンスの構造を定義する
まずはProtocol Buffersと呼ばれる、構造を定義する為の言語を使って、リクエストやレスポンスを定義していきます。
example.proto
syntax = "proto3";
package pb;
message HelloRequest {
int64 id = 1;
string name = 2;
}
message HelloResponse {
int64 id = 1;
string name = 2;
}
わかりにくさを避けるために、同じフィールド定義を持ったHelloRequest
とHelloResponse
を定義しました。
Protocol Buffersで扱える型やその他のシンタックスなどはドキュメントを参照ください。
https://developers.google.com/protocol-buffers
2. 関数を定義する
example.proto
syntax = "proto3";
package pb;
// ... messageの定義など
service ExampleService {
rpc Hello(HelloRequest) returns (HelloResponse);
}
ExampleService
を定義しました。
これはHello関数を持ったインターフェースを自動生成してくれます。
引数にHelloRequest
を取り、戻り値にHelloResponse
を返す事がわかります。
3. 自動生成を実行する
自動生成には、protocと呼ばれる専用のコンパイラが必要です。
まずは以下のようにインストールします。
GithubのReleasesページより、OSに合わせた実行ファイルをダウンロードできます。
https://github.com/protocolbuffers/protobuf/releases
例えば、Linuxの64bit版であれば以下のようにします。
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.10.1/protoc-3.10.1-linux-x86_64.zip
# binディレクトリやreadmeが含まれているので、-dで適当なディレクトリに展開するのをおすすめします
$ unzip -d protoc protoc-3.10.1-linux-x86_64.zip
$ ./protoc/bin/protoc --version
libprotoc 3.10.1
# バージョンが表示されれば成功です
次に、前項で作ったprotoファイルから、gRPC用のコードや、Go言語のコードを出力するためのプラグインをインストールしていきます。
$ go install github.com/golang/protobuf/protoc-gen-go
ここまでできたら準備完了です。
以下のコマンドを実行すると、構文エラーや必要ツールの準備不足がなければ、example.pb.go
ファイルが出力されます。
$ mkdir pb # 出力先のディレクトリを作成しておきます
$ ./protoc/bin/protoc --proto_path=. --go_out=plugins=grpc:./pb example.proto
$ ls
example.pb.go example.proto protoc
# 以下、説明用
$ ./protoc/bin/protoc \
--go_out=plugins=grpc:./pb \ # goファイルの出力にgrpcプラグインを指定、出力先をpbディレクトリに設定します
example.proto # 読み込むprotoファイルを指定します
APIとして呼び出される関数の実装をする
次に、前項で自動生成されたインターフェースを満たす為の実装を行っていきます。
main.go
package main
import (
"context"
"fmt"
"github.com/yukpiz/grpc-example-go/pb"
)
func main() {
}
type Service struct{}
// protocで出力されたインターフェースに準拠するレシーバーを実装する
// 第一引数はcontext.Context、第二引数はリクエスト構造体
// 第一戻り値はレスポンス構造体、第二戻り値はエラーです
func (*Service) Hello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
// 標準出力にログを出力する
fmt.Println("Helloが呼び出されました!")
// レスポンス構造体を生成して、各フィールドに値をセットしています
return &pb.HelloResponse{
Id: req.Id,
Name: req.Name,
}, nil
}
main関数は後の項で実装しますので、一旦中身のない定義で問題ありません。
Service構造体(type Service struct{}
)と、そのレシーバー(func (*Service) Hello(...) ...
)を定義して、内部を実装してください。
ここでは呼び出された時に、fmtパッケージで標準出力に文字列を出力した後、リクエストで受け取ったIDとNameをそのままレスポンスとして返却しています。
エラーを扱いたい場合は、第二戻り値のエラーを使う事ができます。
return nil, xerrors.New("エラーです")
gRPCサーバーとして起動する
次に、gRPCサーバーとして起動する為のmain関数を実装していきます。
先程のmain.goに記述してください。
package main
import (
"context"
"fmt"
"net"
"os"
"github.com/yukpiz/grpc-example-go/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
// gRPCサーバーの生成
server := grpc.NewServer()
// gRPCサーバーにserviceを登録します
svc := &Service{}
pb.RegisterExampleServiceServer(server, svc)
// リフレクションモードの登録
// 後の項で利用します、実行中サーバーの定義を取得したりできるようになります
reflection.Register(server)
// localhost:1111で接続を待ち受ける準備をします
conn, err := net.Listen("tcp", ":1111")
if err != nil {
fmt.Printf("network I/O error: %v", err)
os.Exit(1)
}
fmt.Println("...Waiting for localhost:1111")
// gRPCサーバーを起動します、localhost:1111で接続できます
if err := server.Serve(conn); err != nil {
fmt.Printf("serve error: %v", err)
os.Exit(1)
}
}
ここまででgRPCサーバーの実装は完了です。
以下のコマンドでサーバーを起動してみてください。
$ go run main.go
...Waiting for localhost:1111
# 上記のログが出力されて、panicなど起こさなければ成功です
ツールを使って呼び出してみる
前の項で、gRPCサーバーを実装して、起動するところまでできました。
最後に起動したgRPCサーバーを呼び出してみます。
gRPCはHTTP/2通信を利用する為、通常のWebサーバーとは違い、curlやブラウザで呼び出す事ができません。
デバッグの為にAPIを呼び出すには、以下の方法があります。
- 専用のCLIツールを利用する(grpccurlやEvansなど)
- gRPCクライアントを実装する(Go言語に限らず、様々な言語で実装が可能です)
今回はより簡単に試す為に、EvansというCLIツールを使います。
詳細な使い方などはGithubの公式ページを参照ください。
Evans - Github
まずはCLIツールをインストールします。
$ go get github.com/ktr0731/evans
$ evans --version
evans 0.8.3
$ evans --host localhost --port 1111 -r
localhost:1111>
localhost:1111> package pb # 利用するパッケージを指定します
localhost:1111> service ExampleService # 利用するサービスを指定します
# APIを呼び出します
# リクエストを指定すると、APIが呼び出されます
localhost:1111> call Hello
id (TYPE_INT64) => 1
name (TYPE_STRING) => yukpiz
{
"id": "1",
"name": "yukpiz"
}
リフレクションモードを利用している場合、protoの定義などを読み込まずに-r
オプションで起動する事ができます。
最後のcall Hello
を実行して、APIのリクエストデータを指定すると、APIが呼び出されて結果が返ってきます。
起動したgRPCサーバーでは、呼び出された際のログが出力されています。
$ go run main.go
...Waiting for localhost:1111
Helloが呼び出されました!
おわりに
Protocol Buffersのコードの自動生成や、CLIツールなど覚える必要のあることは多いですが、
gRPCサーバーの起動と呼び出しができるようになりました。
今回は最小限のシンプルな実装になっていますが、
protocのプラグインやProtocol Buffersの様々な構文を使ってさらに複雑なAPIサーバーを実装することができます。
- proto3のimportを使って複数のprotoファイルでAPI定義を行う
- protoc-gen-validateを使ったデータ検証コードの自動生成
- protoc-gen-docを使ったドキュメントの自動生成
- protoc-gen-gotagを使った構造体タグの自動生成
などなど、、
実際の運用で使える様々な機能が取り揃っています。
今回、記事の為に作成したサンプルコードはGithubに公開してあります。
自由に使ってください。
この記事を書くにあたり、参考にさせて頂いた記事やソースコードです。
ありがとうございます<(_ _*)>