gRPC入門:Protocol Buffersの定義からサーバー・クライアント実装まで

スポンサーリンク

gRPC はGoogleが開発したRPC(Remote Procedure Call)フレームワークです。Protocol Buffers(protobuf)でインターフェイスを定義し、複数言語のコードを自動生成できます。REST APIと比べてバイナリ通信により高速で、型安全なAPI設計が可能です。

REST API との比較

項目 REST API gRPC
通信形式 JSON(テキスト) Protocol Buffers(バイナリ)
インターフェイス定義 OpenAPI(任意) .proto ファイル(必須)
コード生成 ツール依存 protoc で自動生成
ストリーミング 非対応(SSE等で補完) 双方向ストリーミング対応
ブラウザからの直接利用 基本的に不可(gRPC-Webが必要)
向いているケース 外部公開API マイクロサービス間通信

Protocol Buffers(protobuf)とは

gRPCのインターフェイス定義言語です。.proto ファイルにメッセージ型とサービスを定義し、protoc コンパイラで各言語のコードを生成します。

syntax = "proto3";

message User {
  int32  id    = 1;
  string name  = 2;
  string email = 3;
}

フィールドに付く数字(= 1= 2)はタグ番号です。バイナリエンコード時の識別子になるため、一度決めたら変更しません。

.proto ファイルの書き方

メッセージ型

syntax = "proto3";

package user;

message User {
  int32  id         = 1;
  string name       = 2;
  string email      = 3;
  bool   is_active  = 4;
}

message GetUserRequest {
  int32 id = 1;
}

message ListUsersResponse {
  repeated User users = 1;
}

主なスカラー型

proto型 Go Python
string string str
int32 int32 int
int64 int64 int
bool bool bool
float float32 float
bytes []byte bytes

サービス定義

syntax = "proto3";

package user;

option go_package = "./pb";

service UserService {
  rpc GetUser    (GetUserRequest)    returns (User);
  rpc ListUsers  (ListUsersRequest)  returns (ListUsersResponse);
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse);
}

message GetUserRequest    { int32 id = 1; }
message ListUsersRequest  {}
message CreateUserRequest { string name = 1; string email = 2; }
message DeleteUserRequest { int32 id = 1; }
message DeleteUserResponse { bool success = 1; }
message ListUsersResponse  { repeated User users = 1; }

message User {
  int32  id    = 1;
  string name  = 2;
  string email = 3;
}

ツールのインストール

protoc(macOS)

brew install protobuf
protoc --version

Go用プラグイン

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Python

pip install grpcio grpcio-tools

コードの生成

Go

protoc   --go_out=.   --go_opt=paths=source_relative   --go-grpc_out=.   --go-grpc_opt=paths=source_relative   proto/user.proto

user.pb.go(メッセージ型)と user_grpc.pb.go(サービスインターフェイス)が生成されます。

Python

python -m grpc_tools.protoc   -I.   --python_out=.   --grpc_python_out=.   proto/user.proto

サーバー実装(Go)

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "example.com/myapp/pb"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
}

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    return &pb.User{
        Id:    req.Id,
        Name:  "田中 太郎",
        Email: "tanaka@example.com",
    }, nil
}

func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
    return &pb.User{
        Id:    1,
        Name:  req.Name,
        Email: req.Email,
    }, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &userServer{})
    log.Println("gRPC server listening on :50051")
    s.Serve(lis)
}

クライアント実装(Go)

package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    pb "example.com/myapp/pb"
)

func main() {
    conn, _ := grpc.Dial("localhost:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    defer conn.Close()

    client := pb.NewUserServiceClient(conn)
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
    if err != nil {
        log.Fatalf("GetUser failed: %v", err)
    }
    log.Printf("User: id=%d name=%s", user.Id, user.Name)
}

ストリーミング

gRPCには4種類の通信パターンがあります。

パターン .proto の書き方 用途
Unary rpc Get(Req) returns (Res) 通常のリクエスト/レスポンス
Server streaming rpc List(Req) returns (stream Res) サーバーが複数レスポンスを返す
Client streaming rpc Upload(stream Req) returns (Res) クライアントが複数送信
Bidirectional rpc Chat(stream Req) returns (stream Res) 双方向リアルタイム

Server streaming の例

service UserService {
  rpc ListUsers (ListUsersRequest) returns (stream User);
}
// サーバー側
func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    users := []pb.User{{Id: 1, Name: "田中"}, {Id: 2, Name: "鈴木"}}
    for _, u := range users {
        stream.Send(&u)
    }
    return nil
}

// クライアント側
stream, _ := client.ListUsers(ctx, &pb.ListUsersRequest{})
for {
    user, err := stream.Recv()
    if err == io.EOF { break }
    log.Printf("受信: %s", user.Name)
}

動作確認ツール:grpcurl

curl のgRPC版です。

# インストール
brew install grpcurl

# サービス一覧(リフレクション有効時)
grpcurl -plaintext localhost:50051 list

# メソッド呼び出し
grpcurl -plaintext   -d '{"id": 1}'   localhost:50051   user.UserService/GetUser

サーバー側でリフレクションを有効にするには:

import "google.golang.org/grpc/reflection"

s := grpc.NewServer()
reflection.Register(s)

まとめ

1. .proto ファイルにメッセージ型とサービスを定義する
2. protoc でサーバー・クライアントのコードを生成する
3. 生成されたインターフェイスを実装してサーバーを起動する
4. 生成されたクライアントコードでメソッドを呼び出す
要素 役割
.proto APIのインターフェイス定義
protoc コード生成ツール
50051 gRPCのデフォルトポート
grpcurl コマンドラインから動作確認

REST APIとの比較・設計については「OpenAPI入門:スキーマ定義からモックサーバーまで」も参考にしてください。

curlでREST APIを叩く方法は「curl入門:GET・POST・PUT・認証・クッキーの使い方まとめ」を参照してください。