#0119（2025/05/04）gRPC入門

## はじめに

本記事では、Googleが開発した高速・汎用的なRPCフレームワークである **gRPC** について、以下の内容を整理して解説します。

1. **gRPCとは**（概要、特徴）
2. **gRPCが実際に利用されている場面**
3. **類義技術との違い**（GraphQL, REST）
4. **Pythonサンプルコード**
5. **いつgRPCを使うか**

---

## 1. gRPCとは

### 概要

* **Remote Procedure Call (RPC)** の仕組みを採用し、リモートの関数／メソッドを呼び出す。
* Google製のオープンソースで、HTTP/2とProtocol Buffersをベースに設計。

### 特徴

* **高速・軽量**: バイナリシリアライズ（Protocol Buffers）を使い、JSONより効率的。
* **HTTP/2**: 多重化・ヘッダ圧縮・双方向ストリーミングをサポート。
* **多言語対応**: C++, Java, Python, Go, Node.jsなど多くの言語で公式実装。
* **自動コード生成**: `.proto`ファイルからクライアント／サーバーのスタブを生成。
* **双方向ストリーミング**: クライアント⇄サーバー間で同時にデータ送受信可能。

---

## 2. gRPCが実際に利用されている場面

* **マイクロサービス通信**: 内部サービス間の高頻度な呼び出し（Netflix, Dropboxなど）
* **分散システム**: データベースノード間通信（CockroachDB, etcd）
* **機械学習モデル配信**: TensorFlow Serving のAPI
* **サービスメッシュ**: Istio / Envoy によるgRPCトラフィック管理
* **モバイル／IoT**: 通信量を抑えつつリアルタイム性を担保（Square(Block)など）

---

## 3. 類義技術との違い（GraphQL, REST）

| 比較項目    | gRPC                   | GraphQL                      | REST API          |
| ------- | ---------------------- | ---------------------------- | ----------------- |
| データ形式   | バイナリ（Protocol Buffers） | テキスト（JSON）                   | テキスト（JSON, XMLなど） |
| 通信プロトコル | HTTP/2                 | HTTP/1.1 (通常)                | HTTP/1.1 (一般的)    |
| ストリーミング | 双方向ストリーミング対応           | 標準では非対応 (Subscription等オプション) | 基本はリクエスト→レスポンス    |
| 型安全性    | 高い（IDLで定義）             | スキーマ定義あり                     | ランタイム検証が主         |
| 柔軟性     | 定義済RPCインターフェースに従う      | クエリでフィールド選択可能                | エンドポイントごとに設計      |
| 主な用途    | サービス間通信／リアルタイム処理       | フロントエンド向け柔軟データ取得             | 公開API／ブラウザ連携      |

---

## 4. Pythonサンプルコード

以下の例では、シンプルな「Hello World」RPCに加え、クライアントサイド、サーバーサイド両方でのストリーミング例も紹介します。

### 4.1. ディレクトリ構成

```text
grpc-python-example/
├── protos/
│   └── helloworld.proto
├── server/
│   ├── server.py
│   └── requirements.txt
└── client/
    ├── client.py
    └── requirements.txt
```

### 4.2. 必要パッケージのインストール

各ディレクトリで以下を実行:

```bash
pip install grpcio grpcio-tools
```

### 4.3. Protocol Buffers 定義 (`protos/helloworld.proto`)

```proto
syntax = "proto3";
package helloworld;

service Greeter {
  // 単方向Unary RPC
  rpc SayHello (HelloRequest) returns (HelloReply);

  // サーバーストリーミングRPC
  rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply);

  // クライアントストリーミングRPC
  rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply);

  // 双方向ストリーミングRPC
  rpc SayHelloBiDiStream (stream HelloRequest) returns (stream HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```

> .protoファイルは、gRPC（Google Remote Procedure Call）やProtocol Buffersなどのデータ交換フォーマットで使用される、スキーマ定義ファイルです。クライアントとサーバー間でデータの構造を共有し、データ交換の互換性を確保するために使われます。

#### コード生成コマンド

プロジェクトルートで実行:

```bash
python -m grpc_tools.protoc \
  -I=protos \
  --python_out=server \
  --grpc_python_out=server \
  --python_out=client \
  --grpc_python_out=client \
  protos/helloworld.proto
```

### 4.4. サーバー実装 (`server/server.py`)

```python
from concurrent import futures
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
import time

class GreeterServicer(helloworld_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(
            message=f"[Unary] Hello, {request.name}!"
        )

    def SayHelloServerStream(self, request, context):
        for i in range(5):
            yield helloworld_pb2.HelloReply(
                message=f"[ServerStream] Hello {request.name}, msg {i+1}"
            )
            time.sleep(0.5)

    def SayHelloClientStream(self, request_iterator, context):
        names = [req.name for req in request_iterator]
        combined = ",".join(names)
        return helloworld_pb2.HelloReply(
            message=f"[ClientStream] Hello {combined}!"
        )

    def SayHelloBiDiStream(self, request_iterator, context):
        for req in request_iterator:
            yield helloworld_pb2.HelloReply(
                message=f"[BiDiStream] Hello {req.name}!"
            )


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(
        GreeterServicer(), server
    )
    server.add_insecure_port('[::]:50051')
    server.start()
    print("gRPC server started on port 50051")
    server.wait_for_termination()

if __name__ == '__main__':
    serve()
```

### 4.5. クライアント実装 (`client/client.py`)

```python
import grpc
import helloworld_pb2
import helloworld_pb2_grpc

def run():
    channel = grpc.insecure_channel('localhost:50051')
    stub = helloworld_pb2_grpc.GreeterStub(channel)

    # 1) Unary RPC
    resp1 = stub.SayHello(
        helloworld_pb2.HelloRequest(name='name1')
    )
    print(resp1.message)

    # 2) Server Streaming
    for reply in stub.SayHelloServerStream(
        helloworld_pb2.HelloRequest(name='StreamUser')
    ):
        print(reply.message)

    # 3) Client Streaming
    names = ['Alice', 'Bob', 'Charlie']
    requests = (helloworld_pb2.HelloRequest(name=n) for n in names)
    resp3 = stub.SayHelloClientStream(requests)
    print(resp3.message)

    # 4) BiDi Streaming
    def request_generator():
        for n in ['X', 'Y', 'Z']:
            yield helloworld_pb2.HelloRequest(name=n)
    for reply in stub.SayHelloBiDiStream(request_generator()):
        print(reply.message)

if __name__ == '__main__':
    run()
```

---

## 5. いつgRPCを使うか

いつgRPCを使うか

以下のような要件がある場合、gRPCの採用を検討すると良いでしょう。

* **高パフォーマンスが必須**：低レイテンシ・少ない帯域消費を実現したい
* **双方向ストリーミング**：リアルタイムデータの送受信が必要
* **マイクロサービス間通信**：多言語スタックのサービス間で型安全に連携したい
* **厳密なIDL管理**：API仕様を明確に定義して自動生成したい
* **既存のHTTP/2インフラがある**：ロードバランサやサービスメッシュでHTTP/2を活かせる