はじめに
スターティングgRPC という本を読んだので、そのアウトプット記事を書きます。
言語は Go です。
本を読む前の筆者のレベル感は以下であり、想定読者も同様になります。
- Go は書いたことある
- gRPC は 1mm もわからん
本記事では gRPC 未経験者が最低限のサーバーサイド開発をできるレベルに到達することを目標とします。
ベースは スターティングgRPC ですが、最低限の内容のみを抜き出しています。
特に「そもそも gRPC とはどいう技術か?」「既存技術と比較したときのメリット・デリットは?」「gRPC の採用判断はどのようにすべきか?」などは本の方にかなりわかりやすく書かれているため、ぜひ一読をおすすめします。
筆者は Docker でサクっと環境を作り学習しました。
学習時のソースコードは GitHub にアップロード しています。
本記事の範囲
本記事では gRPC とは何か?から始まり、実際の実装の流れまでを記述します。
実装では .proto ファイルを書き、コードを自動生成し、生成されたインターフェースを実装することになります。
最後に gPRC クライントでの動作確認を行います。
以下、本記事の範囲の詳細になります。
含むもの
- gRPC とは何か?
- gRPC の長所は何か?
- .proto ファイルの書き方
- gRPC サーバー実装
含まないもの
- PRC とは何か?
- REST との違いは何か?
- gRPC の短所は何か?
- gRPC が対応しているストリーミング形式
- 双方向/双方向ストリーミングの実装方法
くどいようですが、上記はすべて スターティングgRPC に書いてあります。
本当におすすめです。
gRPC とは何か?
gRPC とは、以下の2つを実現する RPC フレームワークである。
- 高速な API 通信
- スキーマ駆動家初
現在、マイクロサービス間の内部通信の実現方法として有力視されている。
なぜ gRPC を使用するのか?
REST と比較し、2つの長所があるためである。
① HTTP/2 による高速通信
gRPC では HTTP/2 を採用しており、 HTTP/1.1 を採用する REST よりも高速である。
HTTP/2 ではデータ形式はバイナリであり、コネクションの接続・切断を都度行わなくて良い点が特徴である。
② スキーマ駆動開発による高生産性
gRPC では Protocol Buffers を採用している。
Protocol Buffers ではスキーマを定義し、コードを自動生成する。
スキーマ定義 → 自動生成の仕組みにより、API ドキュメントの管理が不要となる。
スキーマ = API ドキュメントとなるためである。
クラアントがサービスを利用したい場合、スキーマを見ればすべて書いてある。
バックエンドがサービスを追加・変更したい場合、スキーマを書けばコードを自動生成できる。
.proto ファイルの記述
Protocol Buffers では、スキーマは *.proto
というファイルに独自の IDL で記述する。
実際の書き方は以下の記事で。
protoc によるコード生成
前提条件
Go を利用する場合、以下の3つのパッケージが必要である。
- コンパイラ
- protoc
- コードジェネレータ
- protoc-gen-go
- protoc-gen-go-grpc
protoc のインストール
Mac の場合、以下でインストールできる。
$ brew install protobuf
コードジェネレータのインストール
Go をインストール済みの場合、以下でインストールできる。
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
生成コマンド
protoc
コマンドでコードの自動生成が可能である。
$ protoc --go_out=path/to/output_dir --go-grpc_out=path/to/output_dir --go-grpc_opt=require_unimplemented_servers=false path/to/.proto_file
Go と gRPC のオプションを指定
Go と gRPC の2つのオプションを指定していることに注意。
Brotocol Buffers は何も Go 専門でもなければ、 gRPC 専門でもない。
そのため、生成先コードとして両方を指定する必要がある。
require_unimplemented_servers オプション
デフォルトで true
である。
英語で調べても情報が出てこないので、正直あまり理解していない。
ざっくりインターフェースが実装されていない場合にエラーを返すメソッドと捉えている。
true
を指定した場合は実装が求められるが、何を実装すればよいかイマイチわからない。
緊急性もなさそうなのでオプションで off にすることにより対応。
インターフェースの実装
ptoroc
コマンドにより生成されたインターフェースを実装するコードを書く。
ハンドラー
基本的に型は自動生成されているため、行いたい処理を実装するだけでOKである。
コード例は後ほど。
ハンドラーの登録
ハンドラーを登録し、サーバーにエンドポイントを追加する。
server := grpc.NewServer()
api.RegisterXXXServiceServer(server, handler.NewXXXHandle())
server.Serve()
簡単なコード例
以下に簡単なコード例を示す。
パッケージ周りはザルであるが、勘弁願いたい。
実際に動くソースコードは GitHub に置いている。
.proto ファイル
syntax = "proto3";
package user;
option go_package = "gen/api";
service UserGetService {
rpc Get(UserRequest) returns (UserResponse) {}
}
message User {
int32 id = 1;
string name = 2;
}
message UserRequest {
int32 id = 1;
}
message UserResponse {
User user = 1;
}
ハンドラー
package handler
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"user/api/gen/api"
)
type userHandler struct{}
func NewUserHandle() *userHandler {
return &userHandler{}
}
func (h *userHandler) Get(ctx context.Context, r *api.UserRequest) (*api.UserResponse, error) {
if r.Id == 0 {
return nil, status.Errorf(codes.InvalidArgument, "ユーザーの ID を指定してください")
}
res := &api.UserResponse{
User: &api.User{
Id: 24,
Name: "John",
},
}
return res, nil
}
サーバー
package main
import (
"fmt"
"log"
"net"
"os"
"os/signal"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"user/api/gen/api"
"user/handler"
)
const port = 50052
func main() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
server := grpc.NewServer()
api.RegisterUserGetServiceServer(server, handler.NewUserHandle())
// To debug on grpc_cli.
reflection.Register(server)
go func() {
log.Printf("start gRPC server port: %v", port)
server.Serve(lis)
}()
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("stopping gRPC server...")
server.GracefulStop()
}
gRPC クライアント
動作確認のために gRPC を叩きたくなります。
ただし、cURL ではレスポンスを可読することはできません。
Protocol Bufffers はデータ形式がバイナリだからです。
そのため、gRPC 専用のクライアントを使用する必要があります。
今回は grpc_cli を使用します。
エンドポイントを叩く
grpc_cli のインストール
Mac の場合は以下でインストールできる。
$ brew tap grpc/grpc
$ brew install grpc
叩いてみる
コードの実装例の場合、以下のコマンドでエンドポイントを叩ける。
$ grpc_cli call localhost:50052 user.UserGetService.Get 'id: 1'
connecting to localhost:50052
user {
id: 24
name: john
}
Rpc succeeded with OK status
スキーマ定義を見る
サーバーを reflect 可能にする
サーバーを reflection に登録します。
スキーマ定義はデフォルトでは外部呼び出しから見ることはできません。
本番運用では必要ない機能だからです。
この設定をすることで、 gRPC クライアントからスキーマ定義を参照可能になります。
import (
...
"google.golang.org/grpc/reflection"
)
func main() {
...
reflection.Register(server)
}
ls する
grpc_cli ls
コマンドでスキーマ定義を見ることができます。
ファイルを見たほうが速いか、cli から見たほうが速いかは人によりそうですね。
$ grpc_cli ls localhost:50052 user.UserGetService -l
filename: user.proto
package: user;
service UserService {
rpc Get(user.UserRequest) returns (user.UserResponse) {}
}
終わりに
モダンらしいとは聞きつつ中々手がつけられなかった gRPC の学習・解説をしました。
本では一部古くなっていた箇所も改修してコードにしています。
もし役に立ちましたら LGTM 押して頂けると励みになります。