gRPC
gRPC 完全ガイド: 高性能RPCフレームワークの全容
1. はじめに
1.1 gRPCとは
gRPC(gRPC Remote Procedure Call)は、Googleが開発したオープンソースの高性能RPCフレームワークである。2015年にGoogleが社内で使用していたStubbyと呼ばれるRPC基盤の後継として公開され、現在はCloud Native Computing Foundation(CNCF)のもとで管理されている。
gRPCの「g」はリリースバージョンごとに異なる意味が割り当てられており(例: gRPC、good、green、glamorousなど)、一貫した頭字語ではない。しかし、その技術的な意味は明確である。gRPCは、クライアントアプリケーションがローカルオブジェクトのメソッドを呼び出すかのように、別のマシン上のサーバーアプリケーションのメソッドを直接呼び出すことを可能にするフレームワークである。
1.2 従来のAPI通信方式との比較
現代のマイクロサービスアーキテクチャにおいて、サービス間通信は以下のような方式が広く利用されている。
| 特性 | REST (HTTP/1.1 JSON) | GraphQL | gRPC |
|---|---|---|---|
| プロトコル | HTTP/1.1 | HTTP/1.1 | HTTP/2 |
| データ形式 | JSON/XML | JSON | Protocol Buffers (バイナリ) |
| スキーマ定義 | OpenAPI(任意) | SDL(必須) | .proto(必須) |
| コード生成 | ツール依存 | ツール依存 | ネイティブサポート |
| ストリーミング | 限定的 | Subscription | 双方向ストリーミング |
| ブラウザサポート | ネイティブ | ネイティブ | gRPC-Web経由 |
| パフォーマンス | 低〜中 | 中 | 高 |
| 型安全性 | 低 | 中 | 高 |
1.3 gRPCが解決する課題
gRPCは以下の課題を解決するために設計されている。
- パフォーマンス: HTTP/2とProtocol Buffersの組み合わせにより、JSONベースのREST APIと比較して最大10倍のスループット向上を実現する
- 型安全性:
.protoファイルによる厳密なインターフェース定義により、コンパイル時に型の不整合を検出できる - 多言語対応: 単一の
.proto定義から10以上の言語のクライアント・サーバーコードを自動生成できる - 双方向ストリーミング: HTTP/2のストリーム多重化により、単一のTCPコネクション上で複数のリクエスト・レスポンスを同時に処理できる
- 契約駆動開発: サービスのインターフェースが明示的に定義され、クライアントとサーバーの開発を独立して進められる
1.4 gRPCの採用事例
gRPCは以下のような大規模システムで採用されている。
- Google: 内部のマイクロサービス間通信(毎秒数十億のRPCコール)
- Netflix: マイクロサービスアーキテクチャの通信基盤
- Slack: リアルタイムメッセージング基盤
- Uber: サービスメッシュにおけるサービス間通信
- Square: 決済処理システム
- CoreOS (etcd): 分散キーバリューストアのクライアント通信
- Kubernetes: コンポーネント間通信(kubeletとコンテナランタイム間のCRI)
- Envoy Proxy: xDS APIによる動的設定配信
2. gRPCの基盤技術
2.1 HTTP/2
gRPCはトランスポート層としてHTTP/2を採用している。HTTP/2は以下の特徴を持ち、gRPCの高性能を支えている。
2.1.1 バイナリフレーミング
HTTP/1.1がテキストベースのプロトコルであるのに対し、HTTP/2はバイナリフレームを使用する。すべての通信はフレームと呼ばれる小さなメッセージに分割され、バイナリ形式でエンコードされる。
HTTP/2 フレーム構造:
+-----------------------------------------------+
| Length (24bit) |
+---------------+-------------------------------+
| Type (8bit) | Flags (8bit) |
+-+-------------+-------------------------------+
|R| Stream Identifier (31bit) |
+=+=============+===============================+
| Frame Payload |
+-----------------------------------------------+
主要なフレームタイプ:
- DATA: リクエスト/レスポンスのボディデータ
- HEADERS: HTTPヘッダー情報
- SETTINGS: 接続設定パラメータ
- WINDOW_UPDATE: フロー制御のウィンドウサイズ更新
- RST_STREAM: ストリームの異常終了
- GOAWAY: コネクションの正常終了通知
2.1.2 ストリーム多重化
HTTP/1.1では1つのTCPコネクションで同時に1つのリクエスト/レスポンスしか処理できず(Head-of-Line Blocking問題)、並列処理には複数のTCPコネクションが必要だった。HTTP/2では単一のTCPコネクション上で複数のストリームを多重化でき、各ストリームが独立したリクエスト/レスポンスのペアを処理する。
HTTP/1.1:
Client ──[Request 1]──────────────────> Server
Client <──[Response 1]─────────────────Server
Client ──[Request 2]──────────────────> Server
Client <──[Response 2]─────────────────Server
HTTP/2:
Client ──[Stream 1: Req]──┐
Client ──[Stream 3: Req]──┼──> Server
Client <──[Stream 1: Res]─┤
Client ──[Stream 5: Req]──┤
Client <──[Stream 3: Res]─┤
Client <──[Stream 5: Res]─┘
2.1.3 ヘッダー圧縮(HPACK)
HTTP/2はHPACKと呼ばれるヘッダー圧縮アルゴリズムを使用する。HPACKはハフマン符号化と動的テーブルを組み合わせ、繰り返し送信されるヘッダーを大幅に圧縮する。gRPCではメタデータの送受信にこの圧縮が自動的に適用される。
2.1.4 フロー制御
HTTP/2はコネクションレベルとストリームレベルの2段階のフロー制御を提供する。受信側はWINDOW_UPDATEフレームを送信することで、送信側が送れるデータ量を制御できる。これにより、処理能力の異なるクライアント・サーバー間でも安定した通信が実現される。
2.1.5 サーバープッシュ
HTTP/2ではサーバーがクライアントのリクエストなしにレスポンスを送信できるサーバープッシュ機能がある。ただし、gRPCではこの機能は直接使用されず、代わりにサーバーストリーミングRPCがこの役割を担う。
2.2 Protocol Buffers(protobuf)
Protocol Buffers(protobuf)は、Googleが開発した言語中立・プラットフォーム中立のデータシリアライゼーションフォーマットである。gRPCのデフォルトのシリアライゼーション機構として使用される。
2.2.1 Protocol Buffersの特徴
- 効率的なバイナリエンコーディング: JSONと比較して3〜10倍小さいメッセージサイズ
- 高速なシリアライゼーション/デシリアライゼーション: JSONの20〜100倍の速度
- 前方互換性・後方互換性: フィールドの追加・削除が既存クライアントに影響しない
- 厳密な型定義: スキーマからコードを自動生成し、型安全性を保証
- 自己記述的:
.protoファイルがドキュメントとしても機能
2.2.2 Protocol Buffersのバイナリエンコーディング
Protocol Buffersはタグ-値ペアのバイナリ形式でデータをエンコードする。各フィールドはフィールド番号とワイヤタイプで識別される。
ワイヤタイプ:
0 - Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
1 - 64-bit (fixed64, sfixed64, double)
2 - Length-delimited (string, bytes, embedded messages, repeated fields)
5 - 32-bit (fixed32, sfixed32, float)
例えば、以下のメッセージ定義において:
message Person {
string name = 1;
int32 age = 2;
}
name = "John", age = 30 の場合:
JSON: {"name":"John","age":30} → 27バイト
Protobuf: 0a 04 4a 6f 68 6e 10 1e → 8バイト (約70%削減)
内訳:
0a = フィールド1, ワイヤタイプ2 (length-delimited)
04 = 長さ4バイト
4a 6f 68 6e = "John" (UTF-8)
10 = フィールド2, ワイヤタイプ0 (varint)
1e = 30 (varint エンコーディング)
2.2.3 Varintエンコーディング
Protocol Buffersの整数型はVarintエンコーディングを使用する。これは可変長エンコーディングで、小さな値ほど少ないバイト数で表現できる。
値: 300
バイナリ: 100101100 (9ビット)
Varintエンコーディング:
1. 7ビットグループに分割: 0000010 0101100
2. リトルエンディアン順: 0101100 0000010
3. 継続ビット付加: 10101100 00000010
結果: AC 02 (2バイト)
通常のint32では4バイト必要だが、Varintでは2バイトで済む
2.3 Protocol Buffersの言語仕様(proto3)
2.3.1 基本的なメッセージ定義
// proto3構文を指定(デフォルトはproto2)
syntax = "proto3";
// パッケージ名(名前空間の衝突を防ぐ)
package example.api.v1;
// Go用のパッケージオプション
option go_package = "github.com/example/api/v1;apiv1";
// Java用のオプション
option java_package = "com.example.api.v1";
option java_multiple_files = true;
option java_outer_classname = "UserProto";
// 基本的なメッセージ定義
message User {
// スカラー型フィールド
string user_id = 1;
string name = 2;
string email = 3;
int32 age = 4;
bool is_active = 5;
double score = 6;
// 列挙型
UserRole role = 7;
// ネストされたメッセージ
Address address = 8;
// 繰り返しフィールド(リスト)
repeated string tags = 9;
// マップ型
map<string, string> metadata = 10;
// oneof(排他的フィールド)
oneof contact {
string phone = 11;
string slack_handle = 12;
}
// タイムスタンプ(Well-Known Types)
google.protobuf.Timestamp created_at = 13;
google.protobuf.Timestamp updated_at = 14;
// オプショナルフィールド(proto3ではoptionalキーワードで明示)
optional string nickname = 15;
}
// 列挙型定義
enum UserRole {
USER_ROLE_UNSPECIFIED = 0; // proto3では0がデフォルト値
USER_ROLE_ADMIN = 1;
USER_ROLE_EDITOR = 2;
USER_ROLE_VIEWER = 3;
}
// ネストされたメッセージ
message Address {
string street = 1;
string city = 2;
string state = 3;
string zip_code = 4;
string country = 5;
}
2.3.2 スカラー型の一覧
// Protocol Buffers スカラー型一覧
//
// 整数型:
// int32 - 可変長エンコーディング。負の数には非効率
// int64 - 可変長エンコーディング。負の数には非効率
// uint32 - 可変長エンコーディング。符号なし
// uint64 - 可変長エンコーディング。符号なし
// sint32 - 可変長エンコーディング。負の数に効率的(ZigZagエンコーディング)
// sint64 - 可変長エンコーディング。負の数に効率的(ZigZagエンコーディング)
// fixed32 - 固定4バイト。値が2^28より大きい場合に効率的
// fixed64 - 固定8バイト。値が2^56より大きい場合に効率的
// sfixed32 - 固定4バイト。符号付き
// sfixed64 - 固定8バイト。符号付き
//
// 浮動小数点型:
// float - 32ビット IEEE 754
// double - 64ビット IEEE 754
//
// ブール型:
// bool - true/false
//
// 文字列型:
// string - UTF-8エンコーディングまたは7ビットASCII
// bytes - 任意のバイト列
2.3.3 Well-Known Types
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/any.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
message WellKnownTypesExample {
// Timestamp: 日時を表現
google.protobuf.Timestamp created_at = 1;
// Duration: 時間の長さを表現
google.protobuf.Duration timeout = 2;
// Wrappers: null許容のスカラー型
google.protobuf.StringValue optional_name = 3;
google.protobuf.Int32Value optional_count = 4;
google.protobuf.BoolValue optional_flag = 5;
// Any: 任意の型のメッセージを格納
google.protobuf.Any details = 6;
// Struct: 動的なJSON的構造
google.protobuf.Struct config = 7;
// Empty: 空のメッセージ(引数なし・戻り値なしのRPCに使用)
// rpc Ping(google.protobuf.Empty) returns (google.protobuf.Empty);
// FieldMask: 部分更新のためのフィールド指定
google.protobuf.FieldMask update_mask = 8;
}
2.3.4 フィールド番号のルールとベストプラクティス
message FieldNumberRules {
// フィールド番号は1〜536,870,911(2^29 - 1)の範囲
// 19000〜19999はprotobufの内部使用のため予約
//
// ベストプラクティス:
// - 1〜15: 1バイトエンコーディング。頻繁に使うフィールドに割り当て
// - 16〜2047: 2バイトエンコーディング
// - 2048〜: 3バイト以上
string frequently_used = 1; // 1バイト (フィールド番号1〜15)
string less_frequent = 16; // 2バイト (フィールド番号16〜2047)
// 削除したフィールドの番号は再利用しない(reserved で予約)
reserved 4, 5, 100 to 200;
reserved "old_field_name", "deprecated_field";
}
3. gRPCサービス定義
3.1 サービスの基本構造
gRPCサービスは.protoファイルで定義される。サービスはメソッド(RPC)の集合であり、各メソッドはリクエストメッセージとレスポンスメッセージを持つ。
syntax = "proto3";
package bookstore.v1;
option go_package = "github.com/example/bookstore/v1;bookstorev1";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
// 書籍管理サービスの定義
service BookstoreService {
// Unary RPC: 単一リクエスト→単一レスポンス
rpc GetBook(GetBookRequest) returns (GetBookResponse);
rpc CreateBook(CreateBookRequest) returns (CreateBookResponse);
rpc UpdateBook(UpdateBookRequest) returns (UpdateBookResponse);
rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty);
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);
// Server Streaming RPC: 単一リクエスト→ストリームレスポンス
rpc WatchBooks(WatchBooksRequest) returns (stream BookEvent);
// Client Streaming RPC: ストリームリクエスト→単一レスポンス
rpc UploadBookCover(stream UploadBookCoverRequest) returns (UploadBookCoverResponse);
// Bidirectional Streaming RPC: ストリームリクエスト→ストリームレスポンス
rpc ChatAboutBooks(stream ChatMessage) returns (stream ChatMessage);
}
// === リクエスト/レスポンスメッセージ ===
message GetBookRequest {
string book_id = 1;
}
message GetBookResponse {
Book book = 1;
}
message CreateBookRequest {
Book book = 1;
string idempotency_key = 2; // 冪等性キー
}
message CreateBookResponse {
Book book = 1;
}
message UpdateBookRequest {
Book book = 1;
google.protobuf.FieldMask update_mask = 2; // 部分更新マスク
}
message UpdateBookResponse {
Book book = 1;
}
message DeleteBookRequest {
string book_id = 1;
}
message ListBooksRequest {
int32 page_size = 1; // ページサイズ(最大100)
string page_token = 2; // ページトークン
string filter = 3; // フィルタ式(例: "author = 'Doe'")
string order_by = 4; // ソート順(例: "title asc")
}
message ListBooksResponse {
repeated Book books = 1;
string next_page_token = 2;
int32 total_count = 3;
}
message WatchBooksRequest {
string filter = 1; // 監視対象のフィルタ
}
message BookEvent {
enum EventType {
EVENT_TYPE_UNSPECIFIED = 0;
EVENT_TYPE_CREATED = 1;
EVENT_TYPE_UPDATED = 2;
EVENT_TYPE_DELETED = 3;
}
EventType event_type = 1;
Book book = 2;
google.protobuf.Timestamp event_time = 3;
}
message UploadBookCoverRequest {
oneof data {
BookCoverMetadata metadata = 1; // 最初のメッセージ
bytes chunk = 2; // 後続のデータチャンク
}
}
message BookCoverMetadata {
string book_id = 1;
string content_type = 2; // "image/jpeg", "image/png"
int64 total_size = 3;
}
message UploadBookCoverResponse {
string cover_url = 1;
int64 bytes_received = 2;
}
message ChatMessage {
string user_id = 1;
string content = 2;
google.protobuf.Timestamp timestamp = 3;
}
// === ドメインモデル ===
message Book {
string book_id = 1;
string title = 2;
string author = 3;
string isbn = 4;
string description = 5;
int32 page_count = 6;
repeated string categories = 7;
BookStatus status = 8;
double price = 9;
string currency = 10;
string cover_url = 11;
google.protobuf.Timestamp published_at = 12;
google.protobuf.Timestamp created_at = 13;
google.protobuf.Timestamp updated_at = 14;
}
enum BookStatus {
BOOK_STATUS_UNSPECIFIED = 0;
BOOK_STATUS_DRAFT = 1;
BOOK_STATUS_PUBLISHED = 2;
BOOK_STATUS_OUT_OF_PRINT = 3;
}
3.2 4つの通信パターン
gRPCは4つの通信パターンを提供する。
3.2.1 Unary RPC(単項RPC)
最も基本的なパターン。クライアントが1つのリクエストを送信し、サーバーが1つのレスポンスを返す。従来のHTTP REST APIに最も近い通信パターンである。
Client Server
| |
|------- Request (1回) -------->|
| |
|<------ Response (1回) --------|
| |
ユースケース: CRUD操作、認証、単純なクエリ
3.2.2 Server Streaming RPC(サーバーストリーミングRPC)
クライアントが1つのリクエストを送信し、サーバーがストリームでレスポンスを返す。サーバーはすべてのメッセージを送信後、ストリームを終了する。
Client Server
| |
|------- Request (1回) -------->|
| |
|<------ Response 1 ------------|
|<------ Response 2 ------------|
|<------ Response 3 ------------|
|<------ Response N ------------|
|<------ Stream End ------------|
| |
ユースケース: リアルタイムフィード、大量データの段階的配信、イベント通知
3.2.3 Client Streaming RPC(クライアントストリーミングRPC)
クライアントがストリームでリクエストを送信し、サーバーが1つのレスポンスを返す。サーバーはクライアントがすべてのメッセージを送信し終えてから(または途中で)レスポンスを返す。
Client Server
| |
|------- Request 1 ------------>|
|------- Request 2 ------------>|
|------- Request 3 ------------>|
|------- Request N ------------>|
|------- Stream End ----------->|
| |
|<------ Response (1回) --------|
| |
ユースケース: ファイルアップロード、センサーデータの集約、バッチ処理
3.2.4 Bidirectional Streaming RPC(双方向ストリーミングRPC)
クライアントとサーバーが独立してストリームでメッセージを送受信する。2つのストリームは独立して動作し、任意の順序でメッセージを送受信できる。
Client Server
| |
|------- Request 1 ------------>|
|<------ Response 1 ------------|
|------- Request 2 ------------>|
|------- Request 3 ------------>|
|<------ Response 2 ------------|
|<------ Response 3 ------------|
|------- Stream End ----------->|
|<------ Response 4 ------------|
|<------ Stream End ------------|
| |
ユースケース: チャット、リアルタイムゲーム、協調編集、双方向データ同期
4. gRPCの実装
4.1 開発環境のセットアップ
4.1.1 Protocol Buffersコンパイラのインストール
# macOS
brew install protobuf
# Ubuntu/Debian
sudo apt-get install -y protobuf-compiler
# バージョン確認
protoc --version
# libprotoc 25.1
4.1.2 Go言語での環境構築
# gRPCとProtocol Buffersプラグインのインストール
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# PATHの設定
export PATH="$PATH:$(go env GOPATH)/bin"
# プロジェクト構造
mkdir -p myservice/{cmd/{server,client},proto,internal/{server,client}}
cd myservice
go mod init github.com/example/myservice
go get google.golang.org/grpc
go get google.golang.org/protobuf
4.1.3 Python環境での環境構築
# gRPCツールのインストール
pip install grpcio grpcio-tools grpcio-reflection
# コード生成
python -m grpc_tools.protoc \
-I./proto \
--python_out=./generated \
--grpc_python_out=./generated \
--pyi_out=./generated \
proto/bookstore/v1/bookstore.proto
4.1.4 Java(Gradle)環境での環境構築
// build.gradle
plugins {
id 'java'
id 'com.google.protobuf' version '0.9.4'
}
dependencies {
implementation 'io.grpc:grpc-netty-shaded:1.62.2'
implementation 'io.grpc:grpc-protobuf:1.62.2'
implementation 'io.grpc:grpc-stub:1.62.2'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53'
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.25.3'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.62.2'
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
4.1.5 コード生成コマンド
# Goのコード生成
protoc \
--proto_path=proto \
--go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
proto/bookstore/v1/bookstore.proto
# Buf(推奨ツール)を使用したコード生成
buf generate
4.1.6 Buf設定ファイル
# buf.yaml - モジュール定義
version: v2
modules:
- path: proto
name: buf.build/example/myservice
deps:
- buf.build/googleapis/googleapis
- buf.build/grpc-ecosystem/grpc-gateway
lint:
use:
- STANDARD
except:
- PACKAGE_VERSION_SUFFIX
breaking:
use:
- FILE
---
# buf.gen.yaml - コード生成設定
version: v2
plugins:
# Go
- remote: buf.build/protocolbuffers/go
out: gen/go
opt: paths=source_relative
- remote: buf.build/grpc/go
out: gen/go
opt: paths=source_relative
# Python
- remote: buf.build/protocolbuffers/python
out: gen/python
- remote: buf.build/grpc/python
out: gen/python
# TypeScript (connect-es)
- remote: buf.build/connectrpc/es
out: gen/ts
opt: target=ts
4.2 Go言語での実装例
4.2.1 サーバー実装
// cmd/server/main.go
package main
import (
"context"
"fmt"
"log"
"net"
"os"
"os/signal"
"sync"
"syscall"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
pb "github.com/example/myservice/proto/bookstore/v1"
)
// BookstoreServer はサービスの実装
type BookstoreServer struct {
pb.UnimplementedBookstoreServiceServer // 前方互換性のための埋め込み
mu sync.RWMutex
books map[string]*pb.Book
watchers []chan *pb.BookEvent
}
func NewBookstoreServer() *BookstoreServer {
return &BookstoreServer{
books: make(map[string]*pb.Book),
watchers: make([]chan *pb.BookEvent, 0),
}
}
// Unary RPC: 書籍の取得
func (s *BookstoreServer) GetBook(ctx context.Context, req *pb.GetBookRequest) (*pb.GetBookResponse, error) {
if req.GetBookId() == "" {
return nil, status.Errorf(codes.InvalidArgument, "book_id is required")
}
s.mu.RLock()
defer s.mu.RUnlock()
book, ok := s.books[req.GetBookId()]
if !ok {
return nil, status.Errorf(codes.NotFound, "book not found: %s", req.GetBookId())
}
return &pb.GetBookResponse{Book: book}, nil
}
// Unary RPC: 書籍の作成
func (s *BookstoreServer) CreateBook(ctx context.Context, req *pb.CreateBookRequest) (*pb.CreateBookResponse, error) {
if req.GetBook() == nil {
return nil, status.Errorf(codes.InvalidArgument, "book is required")
}
if req.GetBook().GetTitle() == "" {
return nil, status.Errorf(codes.InvalidArgument, "book.title is required")
}
s.mu.Lock()
defer s.mu.Unlock()
book := req.GetBook()
now := timestamppb.Now()
book.CreatedAt = now
book.UpdatedAt = now
if _, exists := s.books[book.GetBookId()]; exists {
return nil, status.Errorf(codes.AlreadyExists, "book already exists: %s", book.GetBookId())
}
s.books[book.GetBookId()] = book
s.notifyWatchers(&pb.BookEvent{
EventType: pb.BookEvent_EVENT_TYPE_CREATED,
Book: book,
EventTime: now,
})
return &pb.CreateBookResponse{Book: book}, nil
}
// Server Streaming RPC: 書籍イベントの監視
func (s *BookstoreServer) WatchBooks(req *pb.WatchBooksRequest, stream pb.BookstoreService_WatchBooksServer) error {
ch := make(chan *pb.BookEvent, 100)
s.mu.Lock()
s.watchers = append(s.watchers, ch)
s.mu.Unlock()
defer func() {
s.mu.Lock()
for i, w := range s.watchers {
if w == ch {
s.watchers = append(s.watchers[:i], s.watchers[i+1:]...)
break
}
}
s.mu.Unlock()
close(ch)
}()
for {
select {
case event := <-ch:
if err := stream.Send(event); err != nil {
return status.Errorf(codes.Internal, "failed to send event: %v", err)
}
case <-stream.Context().Done():
return stream.Context().Err()
}
}
}
// Client Streaming RPC: 書籍カバー画像のアップロード
func (s *BookstoreServer) UploadBookCover(stream pb.BookstoreService_UploadBookCoverServer) error {
var metadata *pb.BookCoverMetadata
var totalBytes int64
for {
req, err := stream.Recv()
if err != nil {
if err.Error() == "EOF" {
break
}
return status.Errorf(codes.Internal, "failed to receive: %v", err)
}
switch data := req.GetData().(type) {
case *pb.UploadBookCoverRequest_Metadata:
metadata = data.Metadata
case *pb.UploadBookCoverRequest_Chunk:
totalBytes += int64(len(data.Chunk))
}
}
if metadata == nil {
return status.Errorf(codes.InvalidArgument, "metadata not received")
}
coverURL := fmt.Sprintf("https://storage.example.com/covers/%s.jpg", metadata.GetBookId())
return stream.SendAndClose(&pb.UploadBookCoverResponse{
CoverUrl: coverURL,
BytesReceived: totalBytes,
})
}
// Bidirectional Streaming RPC: 書籍についてのチャット
func (s *BookstoreServer) ChatAboutBooks(stream pb.BookstoreService_ChatAboutBooksServer) error {
for {
msg, err := stream.Recv()
if err != nil {
return nil
}
response := &pb.ChatMessage{
UserId: "system",
Content: fmt.Sprintf("You said: %s", msg.GetContent()),
Timestamp: timestamppb.Now(),
}
if err := stream.Send(response); err != nil {
return status.Errorf(codes.Internal, "failed to send: %v", err)
}
}
}
func (s *BookstoreServer) notifyWatchers(event *pb.BookEvent) {
for _, ch := range s.watchers {
select {
case ch <- event:
default:
}
}
}
func main() {
port := os.Getenv("GRPC_PORT")
if port == "" {
port = "50051"
}
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
opts := []grpc.ServerOption{
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 15 * time.Minute,
MaxConnectionAge: 30 * time.Minute,
MaxConnectionAgeGrace: 5 * time.Minute,
Time: 5 * time.Minute,
Timeout: 1 * time.Minute,
}),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 5 * time.Second,
PermitWithoutStream: true,
}),
grpc.MaxRecvMsgSize(10 * 1024 * 1024),
grpc.MaxSendMsgSize(10 * 1024 * 1024),
grpc.MaxConcurrentStreams(1000),
}
grpcServer := grpc.NewServer(opts...)
bookstoreServer := NewBookstoreServer()
pb.RegisterBookstoreServiceServer(grpcServer, bookstoreServer)
healthServer := health.NewServer()
healthpb.RegisterHealthServer(grpcServer, healthServer)
healthServer.SetServingStatus("bookstore.v1.BookstoreService", healthpb.HealthCheckResponse_SERVING)
reflection.Register(grpcServer)
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("Received shutdown signal, gracefully stopping...")
healthServer.SetServingStatus("bookstore.v1.BookstoreService", healthpb.HealthCheckResponse_NOT_SERVING)
grpcServer.GracefulStop()
}()
log.Printf("gRPC server listening on :%s", port)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
4.2.2 クライアント実装
// cmd/client/main.go
package main
import (
"context"
"fmt"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
pb "github.com/example/myservice/proto/bookstore/v1"
)
func main() {
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: 3 * time.Second,
PermitWithoutStream: true,
}),
grpc.WithDefaultServiceConfig(`{
"loadBalancingConfig": [{"round_robin": {}}],
"methodConfig": [{
"name": [{"service": "bookstore.v1.BookstoreService"}],
"waitForReady": true,
"timeout": "30s",
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2.0,
"retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
}
}]
}`),
}
conn, err := grpc.NewClient("localhost:50051", opts...)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewBookstoreServiceClient(conn)
md := metadata.New(map[string]string{
"authorization": "Bearer my-token",
"x-request-id": "req-12345",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
// Unary RPC
createResp, err := client.CreateBook(ctx, &pb.CreateBookRequest{
Book: &pb.Book{
BookId: "book-001",
Title: "gRPC入門",
Author: "山田太郎",
},
IdempotencyKey: "create-book-001",
})
if err != nil {
st, ok := status.FromError(err)
if ok {
log.Printf("gRPC error: code=%s, message=%s", st.Code(), st.Message())
}
return
}
fmt.Printf("Created book: %s\n", createResp.GetBook().GetTitle())
// Server Streaming RPC
go func() {
stream, err := client.WatchBooks(ctx, &pb.WatchBooksRequest{})
if err != nil {
return
}
for {
event, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return
}
fmt.Printf("Book event: type=%s, book=%s\n",
event.GetEventType(), event.GetBook().GetTitle())
}
}()
}
4.3 Python実装例
# server.py
import asyncio
import logging
from concurrent import futures
import grpc
from grpc_health.v1 import health, health_pb2, health_pb2_grpc
from grpc_reflection.v1alpha import reflection
from generated import bookstore_pb2
from generated import bookstore_pb2_grpc
logger = logging.getLogger(__name__)
class BookstoreServicer(bookstore_pb2_grpc.BookstoreServiceServicer):
def __init__(self):
self.books: dict[str, bookstore_pb2.Book] = {}
self.watchers: list[asyncio.Queue] = []
async def GetBook(self, request, context):
if not request.book_id:
await context.abort(grpc.StatusCode.INVALID_ARGUMENT, "book_id is required")
book = self.books.get(request.book_id)
if book is None:
await context.abort(grpc.StatusCode.NOT_FOUND, f"book not found: {request.book_id}")
return bookstore_pb2.GetBookResponse(book=book)
async def WatchBooks(self, request, context):
queue = asyncio.Queue(maxsize=100)
self.watchers.append(queue)
try:
while context.is_active():
try:
event = await asyncio.wait_for(queue.get(), timeout=30.0)
yield event
except asyncio.TimeoutError:
continue
finally:
self.watchers.remove(queue)
async def serve():
server = grpc.aio.server(
futures.ThreadPoolExecutor(max_workers=10),
options=[
('grpc.max_send_message_length', 10 * 1024 * 1024),
('grpc.max_receive_message_length', 10 * 1024 * 1024),
('grpc.keepalive_time_ms', 300000),
('grpc.keepalive_timeout_ms', 60000),
],
)
bookstore_pb2_grpc.add_BookstoreServiceServicer_to_server(BookstoreServicer(), server)
health_servicer = health.HealthServicer()
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server)
service_names = (
bookstore_pb2.DESCRIPTOR.services_by_name["BookstoreService"].full_name,
reflection.SERVICE_NAME,
health.SERVICE_NAME,
)
reflection.enable_server_reflection(service_names, server)
server.add_insecure_port("[::]:50051")
await server.start()
await server.wait_for_termination()
if __name__ == "__main__":
asyncio.run(serve())
5. gRPCインターセプター
5.1 インターセプターの概要
インターセプター(ミドルウェア)は、RPCの処理前後にロジックを挿入する仕組みである。ログ記録、認証、メトリクス収集、エラーハンドリングなどの横断的関心事を実装するために使用される。
5.2 Unaryインターセプター
func UnaryServerLoggingInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
duration := time.Since(start)
st, _ := status.FromError(err)
log.Printf("[gRPC] method=%s duration=%s code=%s", info.FullMethod, duration, st.Code())
return resp, err
}
}
func UnaryServerAuthInterceptor(tokenValidator func(string) (context.Context, error)) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if info.FullMethod == "/grpc.health.v1.Health/Check" {
return handler(ctx, req)
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
}
authHeaders := md.Get("authorization")
if len(authHeaders) == 0 {
return nil, status.Errorf(codes.Unauthenticated, "missing authorization header")
}
token := strings.TrimPrefix(authHeaders[0], "Bearer ")
newCtx, err := tokenValidator(token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
}
return handler(newCtx, req)
}
}
5.3 インターセプターチェーンの構築
func newGRPCServer() *grpc.Server {
chainedUnary := grpc.ChainUnaryInterceptor(
UnaryServerRecoveryInterceptor(),
UnaryServerLoggingInterceptor(),
UnaryServerAuthInterceptor(validateToken),
)
chainedStream := grpc.ChainStreamInterceptor(
StreamServerLoggingInterceptor(),
)
return grpc.NewServer(chainedUnary, chainedStream)
}
6. エラーハンドリング
6.1 gRPCステータスコード
| gRPCコード | 値 | HTTPコード | 説明 |
|---|---|---|---|
| OK | 0 | 200 | 成功 |
| CANCELLED | 1 | 499 | クライアントがキャンセル |
| UNKNOWN | 2 | 500 | 不明なエラー |
| INVALID_ARGUMENT | 3 | 400 | 無効な引数 |
| DEADLINE_EXCEEDED | 4 | 504 | デッドライン超過 |
| NOT_FOUND | 5 | 404 | リソースが見つからない |
| ALREADY_EXISTS | 6 | 409 | リソースが既に存在 |
| PERMISSION_DENIED | 7 | 403 | 権限不足 |
| RESOURCE_EXHAUSTED | 8 | 429 | リソース枯渇(レート制限) |
| FAILED_PRECONDITION | 9 | 400 | 前提条件の失敗 |
| ABORTED | 10 | 409 | 操作の中断 |
| OUT_OF_RANGE | 11 | 400 | 範囲外 |
| UNIMPLEMENTED | 12 | 501 | 未実装 |
| INTERNAL | 13 | 500 | 内部エラー |
| UNAVAILABLE | 14 | 503 | サービス利用不可 |
| DATA_LOSS | 15 | 500 | データ損失 |
| UNAUTHENTICATED | 16 | 401 | 認証エラー |
6.2 リッチエラーモデル(Google Error Model)
import "google.golang.org/genproto/googleapis/rpc/errdetails"
func (s *BookstoreServer) CreateBook(ctx context.Context, req *pb.CreateBookRequest) (*pb.CreateBookResponse, error) {
var violations []*errdetails.BadRequest_FieldViolation
if req.GetBook().GetTitle() == "" {
violations = append(violations, &errdetails.BadRequest_FieldViolation{
Field: "book.title",
Description: "Title is required and cannot be empty",
})
}
if len(violations) > 0 {
st := status.New(codes.InvalidArgument, "invalid book data")
detailedSt, _ := st.WithDetails(&errdetails.BadRequest{FieldViolations: violations})
return nil, detailedSt.Err()
}
// ...
}
// クライアント側でのエラー詳細取得
func handleError(err error) {
st := status.Convert(err)
for _, detail := range st.Details() {
switch d := detail.(type) {
case *errdetails.BadRequest:
for _, v := range d.GetFieldViolations() {
log.Printf("Field: %s, Error: %s", v.GetField(), v.GetDescription())
}
case *errdetails.RetryInfo:
log.Printf("Retry after: %s", d.GetRetryDelay().AsDuration())
}
}
}
7. 認証とセキュリティ
7.1 TLS/SSL認証
# CA証明書の生成
openssl req -x509 -newkey rsa:4096 -days 365 -nodes \
-keyout ca-key.pem -out ca-cert.pem \
-subj "/C=JP/ST=Tokyo/L=Shibuya/O=Example/CN=Example CA"
# サーバー証明書
openssl req -newkey rsa:4096 -nodes \
-keyout server-key.pem -out server-req.pem \
-subj "/C=JP/ST=Tokyo/L=Shibuya/O=Example/CN=localhost"
openssl x509 -req -in server-req.pem -days 365 \
-CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \
-out server-cert.pem -extfile server-ext.cnf
// TLSサーバー設定
func newTLSServer() (*grpc.Server, error) {
serverCert, _ := tls.LoadX509KeyPair("server-cert.pem", "server-key.pem")
caCert, _ := os.ReadFile("ca-cert.pem")
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert, // mTLS
ClientCAs: certPool,
MinVersion: tls.VersionTLS13,
}
return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig))), nil
}
7.2 トークンベース認証
type tokenAuth struct {
token string
}
func (t *tokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{"authorization": "Bearer " + t.token}, nil
}
func (t *tokenAuth) RequireTransportSecurity() bool { return true }
8. ロードバランシングとサービスディスカバリ
8.1 クライアントサイドロードバランシング
conn, _ := grpc.NewClient(
"dns:///myservice.example.com:50051",
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin": {}}]}`),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
8.2 Envoyプロキシベースロードバランシング
static_resources:
clusters:
- name: bookstore_cluster
type: STRICT_DNS
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
health_checks:
- grpc_health_check:
service_name: "bookstore.v1.BookstoreService"
8.3 リトライとヘッジング
リトライ:
Request 1 ──> [FAIL]
Request 2 ──────────> [FAIL]
Request 3 ─────────────────> [SUCCESS]
ヘッジング:
Request 1 ──────────────────> (pending)
Request 2 ──────> [SUCCESS] ← 0.5s後に送信
Request 1 をキャンセル
9. デッドラインとタイムアウト
gRPCは「デッドライン」の概念を使用し、サービス間でデッドラインを伝播させる。
Client (deadline: 10:00:05.000)
→ Service A (remaining: 4.5s)
→ Service B (remaining: 3.2s)
→ Service C (remaining: 1.8s)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.GetBook(ctx, &pb.GetBookRequest{BookId: "book-001"})
10. メタデータとコンテキスト伝播
// クライアント側
md := metadata.New(map[string]string{
"authorization": "Bearer my-token",
"x-request-id": "req-12345",
"x-trace-id": "trace-67890",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
var headerMD, trailerMD metadata.MD
resp, err := client.GetBook(ctx, req,
grpc.Header(&headerMD),
grpc.Trailer(&trailerMD),
)
// サーバー側
md, _ := metadata.FromIncomingContext(ctx)
grpc.SendHeader(ctx, metadata.New(map[string]string{"x-server-version": "2.0.0"}))
grpc.SetTrailer(ctx, metadata.New(map[string]string{"x-request-cost": "0.05"}))
10.1 OpenTelemetry統合
func newTracedServer() *grpc.Server {
return grpc.NewServer(grpc.StatsHandler(otelgrpc.NewServerHandler()))
}
func newTracedClient() (*grpc.ClientConn, error) {
return grpc.NewClient("localhost:50051",
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
}
11. パフォーマンスチューニング
11.1 ベンチマーク比較
| 指標 | REST/JSON | gRPC/Protobuf | 改善率 |
|---|---|---|---|
| シリアライズ速度 | 1,200 ns | 150 ns | 8x |
| デシリアライズ速度 | 1,800 ns | 200 ns | 9x |
| メッセージサイズ | 1,024 B | 320 B | 3.2x |
| Unary RPCレイテンシ | 2.5 ms | 0.8 ms | 3.1x |
| スループット (req/s) | 15,000 | 85,000 | 5.7x |
11.2 メッセージ圧縮
import "google.golang.org/grpc/encoding/gzip"
resp, err := client.ListBooks(ctx, req, grpc.UseCompressor(gzip.Name))
12. gRPC-Gateway(RESTプロキシ)
service BookstoreService {
rpc GetBook(GetBookRequest) returns (GetBookResponse) {
option (google.api.http) = { get: "/v1/books/{book_id}" };
}
rpc CreateBook(CreateBookRequest) returns (CreateBookResponse) {
option (google.api.http) = { post: "/v1/books" body: "book" };
}
}
// gRPC-Gatewayサーバー
mux := runtime.NewServeMux()
pb.RegisterBookstoreServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
http.ListenAndServe(":8080", mux)
13. gRPC-Webとブラウザ対応
13.1 Connect RPC
import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { BookstoreService } from "./gen/bookstore/v1/bookstore_pb";
const transport = createConnectTransport({ baseUrl: "https://api.example.com" });
const client = createClient(BookstoreService, transport);
// Unary RPC
const response = await client.getBook({ bookId: "book-001" });
// Server Streaming
for await (const event of client.watchBooks({})) {
console.log(`Event: ${event.eventType}`);
}
14. ヘルスチェックとリフレクション
# Kubernetes gRPC ヘルスチェック
livenessProbe:
grpc:
port: 50051
service: ""
readinessProbe:
grpc:
port: 50051
service: "bookstore.v1.BookstoreService"
# grpcurlによるデバッグ
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext -d '{"book_id": "book-001"}' localhost:50051 bookstore.v1.BookstoreService/GetBook
15. KubernetesでのgRPCデプロイ
apiVersion: v1
kind: Service
metadata:
name: bookstore-service
spec:
type: ClusterIP
ports:
- port: 50051
targetPort: grpc
appProtocol: grpc
selector:
app: bookstore
15.1 Istioサービスメッシュ統合
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: bookstore-dr
spec:
host: bookstore-service
trafficPolicy:
connectionPool:
http:
h2UpgradePolicy: UPGRADE
loadBalancer:
simple: ROUND_ROBIN
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
16. テスト戦略
16.1 ユニットテスト
func TestGetBook_NotFound(t *testing.T) {
s := NewBookstoreServer()
_, err := s.GetBook(context.Background(), &pb.GetBookRequest{BookId: "nonexistent"})
require.Error(t, err)
st, _ := status.FromError(err)
assert.Equal(t, codes.NotFound, st.Code())
}
16.2 インテグレーションテスト(bufconn)
func setupTestServer(t *testing.T) (pb.BookstoreServiceClient, func()) {
lis := bufconn.Listen(1024 * 1024)
grpcServer := grpc.NewServer()
pb.RegisterBookstoreServiceServer(grpcServer, NewBookstoreServer())
go grpcServer.Serve(lis)
conn, _ := grpc.NewClient("passthrough:///bufconn",
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
return lis.DialContext(ctx)
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
return pb.NewBookstoreServiceClient(conn), func() { conn.Close(); grpcServer.Stop() }
}
16.3 負荷テスト(ghz)
ghz --insecure \
--proto proto/bookstore/v1/bookstore.proto \
--call bookstore.v1.BookstoreService/GetBook \
--data '{"book_id": "book-001"}' \
--concurrency 100 --duration 30s \
localhost:50051
17. 可観測性(Observability)
17.1 Prometheusメトリクス
srvMetrics := grpcprom.NewServerMetrics(
grpcprom.WithServerHandlingTimeHistogram(),
)
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor()),
grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor()),
)
公開されるメトリクス:
grpc_server_handling_seconds_bucket- RPC処理時間grpc_server_handled_total- RPCリクエスト数grpc_server_msg_sent_total/grpc_server_msg_received_total- ストリームメッセージ数
18. API設計のベストプラクティス
18.1 命名規則(Google API Design Guide準拠)
service BookstoreService { ... } // サービス名: 名詞 + Service
rpc GetBook(GetBookRequest) ... // メソッド名: 動詞 + 名詞
message GetBookRequest { ... } // リクエスト: メソッド名 + Request
message GetBookResponse { ... } // レスポンス: メソッド名 + Response
enum BookStatus {
BOOK_STATUS_UNSPECIFIED = 0; // 0は常にUNSPECIFIED
BOOK_STATUS_DRAFT = 1;
}
18.2 ページネーション
message ListBooksRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListBooksResponse {
repeated Book books = 1;
string next_page_token = 2;
int32 total_size = 3;
}
18.3 フィールドマスクによる部分更新
message UpdateBookRequest {
Book book = 1;
google.protobuf.FieldMask update_mask = 2;
}
19. Protocol Buffersの互換性管理
19.1 後方互換な変更
- 新しいフィールドの追加(新番号)
- 新しいメソッド/サービスの追加
- 新しい列挙値の追加
19.2 破壊的変更(避けるべき)
- フィールド番号の変更・再利用
- フィールドの型変更
- サービスメソッドの削除
message Book {
reserved 3; // 古いフィールド番号を予約
reserved "author"; // 古いフィールド名を予約
AuthorInfo author_info = 4; // 新しいフィールドとして追加
}
19.3 Bufによる互換性チェック
buf breaking --against '.git#branch=main'
buf lint
20. gRPCアーキテクチャパターン
20.1 マイクロサービスアーキテクチャでの位置づけ
┌──────────────────┐
│ API Gateway │
│ (gRPC-Gateway) │
└────────┬─────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌─────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
│ User │ │ Catalog │ │ Order │
│ Service │ │ Service │ │ Service │
│ (gRPC) │ │ (gRPC) │ │ (gRPC) │
└────────────┘ └──────────┘ └──────────┘
20.2 Backend for Frontend(BFF)パターン
Mobile App Web App Admin Panel
│ │ │
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌────────────┐
│Mobile BFF│ │ Web BFF │ │ Admin BFF │
│(gRPC-GW) │ │(gRPC-Web) │ │ (gRPC) │
└────┬─────┘ └─────┬─────┘ └──────┬─────┘
└───────────────────┼────────────────────┘
┌─────▼─────┐
│ gRPC │
│ Services │
└───────────┘
21. まとめと今後の展望
21.1 gRPCを選択すべきケース
| ケース | 推奨度 | 理由 |
|---|---|---|
| マイクロサービス間通信 | 非常に高い | 高性能、型安全、多言語対応 |
| リアルタイムストリーミング | 非常に高い | 双方向ストリーミングのネイティブサポート |
| モバイルバックエンド | 高い | 低帯域幅、バッテリー効率 |
| IoTデバイス通信 | 高い | 軽量なバイナリプロトコル |
| 公開API(外部向け) | 中程度 | gRPC-Gateway/Connectでの二重公開が必要 |
| ブラウザ直接通信 | 中程度 | gRPC-Web/Connectが必要 |
| シンプルなCRUD API | 低い | RESTの方がエコシステムが成熟 |
21.2 エコシステム
| カテゴリ | ツール | 説明 |
|---|---|---|
| コード管理 | Buf | Proto管理、リント、互換性チェック |
| API Gateway | gRPC-Gateway | REST<->gRPCブリッジ |
| ブラウザ | Connect-RPC | モダンなgRPC-Web代替 |
| デバッグ | grpcurl / grpcui | CLI/GUIデバッグツール |
| 負荷テスト | ghz | gRPC専用ベンチマークツール |
| サービスメッシュ | Istio / Linkerd | gRPCネイティブサポート |
| プロキシ | Envoy | L7 gRPCロードバランサー |
| 可観測性 | OpenTelemetry | 分散トレーシング |
21.3 今後の展望
- HTTP/3対応: QUICプロトコルベースのHTTP/3対応が進行中。Head-of-Line Blocking問題の解消
- Connect RPCの普及: ブラウザネイティブ対応を含む3プロトコルの単一定義からの提供
- AIワークロード対応: LLM推論サービスなどでの大規模ストリーミングレスポンス処理
- WebTransport: ブラウザからの直接的なgRPC通信の将来的な可能性
- Buf Schema Registry: 組織間でのスキーマ共有と管理の標準化