Go マイクロサービスアーキテクチャと gRPC
gRPC、Protocol Buffers、インターセプター、ストリーミング、本番環境のベストプラクティスを使用して、Go で高性能なバックエンドマイクロサービスを構築するための完全なガイド。
📌 gRPC とは?
gRPC はオープンソースの高性能リモートプロシージャコール(RPC)フレームワークで、2015 年に Google によって開発されました。.proto ファイルでサービスメソッドとメッセージ型を定義すれば、複数の言語で強い型付けを持つクライアントコードとサーバーコードが自動生成されます。
gRPC の「g」は「Google」を意味するのではなく、リリースバージョンごとに異なる意味を持ちます。
gRPC を支える主要技術:
| 技術 | 役割 |
| HTTP/2 | 多重化接続、ヘッダー圧縮、サーバープッシュ |
| Protocol Buffers (protobuf) | バイナリシリアライゼーション — コンパクト、高速、言語中立 |
| コード生成 | .proto 定義からのクライアント/サーバースタブの自動生成 |
⚡ マイクロサービスにおいて REST より gRPC を選ぶ理由
REST は公開 API に優れています。しかし、内部的なサービス間通信では、gRPC は著しい利点を提供します。
| 側面 | REST (HTTP/JSON) | gRPC (HTTP/2 + Protobuf) |
| シリアライゼーション | JSON(テキストベース、冗長) | Protobuf(バイナリ、コンパクト) |
| パフォーマンス | ベースライン | 約 7~10 倍高速なスループット |
| 契約 | OpenAPI/Swagger(オプション) | .proto ファイル(強制) |
| ストリーミング | Websockets(別レイヤー) | ネイティブ双方向ストリーミング |
| コード生成 | 限定的 | 第一級、複数言語対応 |
| ブラウザサポート | ネイティブ | gRPC-Web またはゲートウェイが必要 |
| HTTP バージョン | HTTP/1.1 | HTTP/2 |
実践的な推奨事項: 内部サービス間呼び出しに gRPC を使用します。gRPC-Gateway または API ゲートウェイレイヤーを使用して、ブラウザクライアントと公開 API コンシューマーに REST エンドポイントを公開します。
🏗️ アーキテクチャの概要
┌──────────────┐
│ API Gateway │ (REST ↔ gRPC translation)
│ / gRPC-GW │
└──────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Service A │ │ Service B │ │ Service C │
│ (Go gRPC) │◄──►│ (Go gRPC) │◄──►│ (Go gRPC) │
└─────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Database A │ │ Database B │ │ Message Q │
└───────────┘ └─────────────┘ └─────────────┘
すべてのサービス間呼び出しは HTTP/2 上の gRPC を使用します。
各サービスは独自のデータストアを所有しています。
.proto ファイルはサービス間の契約を定義します。
📋 前提条件とツールセットアップ
1. Protocol Buffer コンパイラ(protoc)のインストール
Linux (apt):
sudo apt install -y protobuf-compiler
macOS (Homebrew):
brew install protobuf
インストールを確認します(バージョン 3+ が必要):
protoc --version
# libprotoc 27.x
どちらのパッケージマネージャーも機能しない場合は、公式 protobuf リリースからプリコンパイル済みバイナリをダウンロードしてください。
2. protoc 用 Go プラグインのインストール
Protocol Buffer コンパイラはネイティブで Go コードを生成しません — 2 つのプラグインが必要です:
# .proto メッセージから Go 構造体を生成
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# Go gRPC サービスインターフェースとスタブを生成
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
重要: $GOPATH/bin が PATH に含まれていることを確認してください:
export PATH="$PATH:$(go env GOPATH)/bin"
このラインを ~/.bashrc、~/.zshrc、または対応するシェルプロファイルに追加して、永続化させます。
3. すべてを検証
protoc --version # protoc コンパイラ
protoc-gen-go --version # Go protobuf プラグイン
protoc-gen-go-grpc --version # Go gRPC プラグイン
📂 プロジェクト構造
クリーンでスケーラブルな Go gRPC マイクロサービスプロジェクトは、このレイアウトに従います:
greeting-app/
├── go.mod
├── go.sum
├── proto/
│ ├── greeting.proto # サービス + メッセージ定義
│ └── greeting/
│ ├── greeting.pb.go # 自動生成:メッセージ構造体
│ └── greeting_grpc.pb.go # 自動生成:gRPC サービススタブ
├── greeter-server/
│ └── main.go # サーバー実装
└── greeter-client/
└── main.go # クライアント実装
本番環境の複数サービスプロジェクトの場合、推奨される構造:
my-platform/
├── proto/ # 共有 .proto 定義
│ ├── user/
│ │ └── user.proto
│ ├── order/
│ │ └── order.proto
│ └── payment/
│ └── payment.proto
├── services/
│ ├── user-service/
│ │ ├── cmd/
│ │ │ └── main.go # エントリーポイント
│ │ ├── internal/
│ │ │ ├── handler/ # gRPC ハンドラー(トランスポート層)
│ │ │ ├── service/ # ビジネスロジック
│ │ │ └── repository/ # データベース層
│ │ └── go.mod
│ ├── order-service/
│ │ └── ...
│ └── payment-service/
│ └── ...
├── pkg/ # 共有ユーティリティ
│ ├── interceptors/
│ ├── config/
│ └── logger/
└── Makefile
設計原則: gRPC をトランスポート層としてのみ扱います。ビジネスロジックを gRPC から独立した別の service パッケージに保ちます。これにより、実行中の gRPC サーバーなしにコードをテストできます。
📝 Protocol Buffers の定義
.proto ファイル
proto/greeting.proto を作成します:
syntax = "proto3";
option go_package = "./greeting";
// リクエストメッセージ — 各フィールドは型、名前、一意のフィールド番号を持ちます
message GreetingRequest {
string name = 1;
}
// レスポンスメッセージ
message GreetingResponse {
string message = 1;
}
// サービス定義 — コンパイラはこれからインターフェースを生成します
service GreetingService {
rpc Greet(GreetingRequest) returns (GreetingResponse);
}
主要概念
メッセージは構造化データの容器です。コンパクトなバイナリ形式にシリアライズされ、ワイア越しに送信されます。各フィールドはバイナリエンコーディングでフィールド番号(名前ではなく)によって識別されます — 本番環境でサービスが稼働している場合、フィールド番号を変更しないことが重要です(これが protobuf が後方互換性を維持する方法です)。
サービスはサーバーが公開する RPC メソッドを定義します。service キーワードはコンパイラに対応する Go インターフェースを生成するよう指示します。
go_package はコンパイラに、出力ディレクトリを基準に生成された Go コードを配置する場所を指示します。
Go コードの生成
プロジェクトルートから以下を実行します:
protoc --go_out=proto --go-grpc_out=proto proto/greeting.proto
| フラグ | 目的 |
--go_out=proto | メッセージ構造体(greeting.pb.go)を proto/ ディレクトリに生成 |
--go-grpc_out=proto | gRPC サービススタブ(greeting_grpc.pb.go)を proto/ ディレクトリに生成 |
これにより 2 つのファイルが生成されます:
-
greeting.pb.go—GreetingRequestとGreetingResponse用の Go 構造体、加えてシリアライゼーションメソッド -
greeting_grpc.pb.go—GreetingServiceServerインターフェース(これを実装)とGreetingServiceClient(これを呼び出す)
🖥️ gRPC サーバーの実装
greeter-server/main.go を作成します:
package main
import (
"context"
"fmt"
"log"
"net"
proto "greeting-app/proto/greeting"
"google.golang.org/grpc"
)
// myGRPCServer は生成された GreetingServiceServer インターフェースを実装します。
// UnimplementedGreetingServiceServer を埋め込むことで前方互換性を確保します —
// .proto ファイルに新しい RPC を追加しても、既存コードは壊れません。
type myGRPCServer struct {
proto.UnimplementedGreetingServiceServer
}
// Greet は Greet RPC の実際のビジネスロジックです。
func (s *myGRPCServer) Greet(
ctx context.Context,
req *proto.GreetingRequest,
) (*proto.GreetingResponse, error) {
log.Printf("Received request to greet: %s", req.Name)
return &proto.GreetingResponse{
Message: "Hello " + req.Name,
}, nil
}
func main() {
port := ":8080"
log.Printf("Starting gRPC server on port %s", port)
// 1. TCP リスナーを作成
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// 2. 新しい gRPC サーバーインスタンスを作成
grpcServer := grpc.NewServer()
// 3. サーバーに実装サービスを登録
proto.RegisterGreetingServiceServer(grpcServer, &myGRPCServer{})
// 4. リクエスト処理を開始
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
何が起こっているか
-
自動生成された
UnimplementedGreetingServiceServerを埋め込む構造体(myGRPCServer)を定義します。これは前方互換性パターンです —.protoファイルに新しい RPC が追加されると、コードは引き続きコンパイルされます(未実装メソッドはデフォルトで「Unimplemented」エラーを返します)。 -
実際のビジネスロジックで
Greetメソッドを実装します。 -
main()関数は TCP リスナーを作成し、gRPC サーバーをインスタンス化し、サービスを登録し、処理を開始します。
📱 gRPC クライアントの実装
greeter-client/main.go を作成します:
package main
import (
"context"
"fmt"
"log"
"time"
proto "greeting-app/proto/greeting"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
fmt.Println("Starting gRPC client...")
// 1. gRPC サーバーへの接続を確立
conn, err := grpc.NewClient(
"localhost:8080",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// 2. 接続から型付きサービスクライアントを作成
client := proto.NewGreetingServiceClient(conn)
// 3. タイムアウトコンテキストを設定
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 4. Greet RPC を呼び出し
response, err := client.Greet(ctx, &proto.GreetingRequest{
Name: "John",
})
if err != nil {
log.Fatalf("Failed to greet: %v", err)
}
fmt.Printf("Response from server: %s\n", response.GetMessage())
}
注:
grpc.Dialとgrpc.WithInsecure()は非推奨です。最新の Go gRPC バージョンでは、grpc.NewClientとgrpc.WithTransportCredentials(insecure.NewCredentials())を使用してください。
▶️ 実行とテスト
ターミナル 1 — サーバーを開始:
go run greeter-server/main.go
# 出力: Starting gRPC server on port :8080
ターミナル 2 — クライアントを実行:
go run greeter-client/main.go
# 出力:
# Starting gRPC client...
# Response from server: Hello John
サーバーターミナルにも以下が表示されるはずです:
Received request to greet: John
インポートエラーが発生した場合は、go mod tidy を実行して依存関係を解決してください。
🔄 gRPC ストリーミングパターン
gRPC は単純なリクエスト-レスポンスではなく、4 つの通信パターンをサポートしています:
1. Unary RPC (リクエスト → レスポンス)
rpc Greet(GreetingRequest) returns (GreetingResponse);
標準的な単一リクエスト、単一レスポンス。通常の関数呼び出しのような形です。
2. サーバーストリーミング (リクエスト → レスポンスのストリーム)
rpc ListGreetings(ListRequest) returns (stream GreetingResponse);
クライアントが 1 つのリクエストを送信し、サーバーが時間とともに複数のレスポンスを送り返します。大規模なデータセットの取得、ライブフィード、またはページネーション結果に適しています。
// サーバー実装
func (s *myServer) ListGreetings(
req *proto.ListRequest,
stream proto.GreetingService_ListGreetingsServer,
) error {
names := []string{"Alice", "Bob", "Charlie"}
for _, name := range names {
if err := stream.Send(&proto.GreetingResponse{
Message: "Hello " + name,
}); err != nil {
return err
}
}
return nil
}
3. クライアントストリーミング (リクエストのストリーム → レスポンス)
rpc RecordGreetings(stream GreetingRequest) returns (GreetingSummary);
クライアントが複数のメッセージを送信し、サーバーが単一のサマリーを送り返します。チャンク単位でのデータアップロードやバッチ操作に適しています。
4. 双方向ストリーミング (ストリーム ↔ ストリーム)
rpc ChatGreetings(stream GreetingRequest) returns (stream GreetingResponse);
クライアントとサーバーの両方が独立してストリームを送信します。リアルタイムチャット、協調編集、またはライブダッシュボードに適しています。
🛡️ インターセプター(ミドルウェア)
インターセプターは HTTP ミドルウェアと同等の gRPC です。ビジネスロジックに触れることなく、RPC 実行前後にロジック(ロギング、認証、レート制限、トレーシング)を注入できます。
4 つのタイプがあります(2 つの側面 × 2 つのモード):
| Unary | Streaming | |
| Server | UnaryServerInterceptor | StreamServerInterceptor |
| Client | UnaryClientInterceptor | StreamClientInterceptor |
ロギングインターセプターの例
func loggingInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
start := time.Now()
// 実際の RPC ハンドラーを呼び出す
resp, err := handler(ctx, req)
// 呼び出しが完了した後にログ出力
log.Printf(
"method=%s duration=%s error=%v",
info.FullMethod,
time.Since(start),
err,
)
return resp, err
}
認証インターセプターの例
func authInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
// メタデータを抽出(gRPC の HTTP ヘッダーに相当)
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "no metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 || !isValidToken(tokens[0]) {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
return handler(ctx, req)
}
複数のインターセプターをチェーン
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(
loggingInterceptor,
authInterceptor,
rateLimitInterceptor,
),
grpc.ChainStreamInterceptor(
streamLoggingInterceptor,
streamAuthInterceptor,
),
)
インターセプターは左から右に実行されます:ロギング → 認証 → レート制限。
go-grpc-middleware(コミュニティライブラリ)
go-grpc-middleware パッケージは本番環境対応のインターセプターを提供します:
| インターセプター | 目的 |
auth | AuthFunc 経由のカスタム認証ロジック |
recovery | パニックをキャッチして gRPC エラーに変換 |
logging | zap、logrus、または slog による構造化ロギング |
prometheus | クライアントおよびサーバーメトリクス |
retry | バックオフ付き自動クライアント側再試行 |
validator | protobuf 定義から受信メッセージを自動検証 |
ratelimit | リクエストレートを制御 |
selector | 特定の RPC メソッドのみにインターセプターを適用 |
go get github.com/grpc-ecosystem/go-grpc-middleware/v2
⚠️ エラーハンドリング
gRPC は標準化されたステータスコードを使用します(HTTP ステータスコードに似ていますが、RPC 用に設計されています)。汎用エラーではなく、常に適切なステータスコードを返してください。
一般的な gRPC ステータスコード
| コード | 意味 | 使用するときは |
OK | 成功 | すべてが動作しました |
InvalidArgument | 不正な入力 | フィールドが不足または不正な形式 |
NotFound | リソースが見つからない | ユーザー、レコードなどが見つからない |
AlreadyExists | 重複 | 一意の制約違反 |
PermissionDenied | 禁止 | 認証はされているが認可されていない |
Unauthenticated | 認証情報がない/無効 | トークンがない、または無効なトークン |
Internal | サーバーエラー | 予期しない障害 |
Unavailable | サービスダウン | 一時的 — 安全に再試行可能 |
DeadlineExceeded | タイムアウト | クライアントのデッドラインに到達 |
ResourceExhausted | レート制限 | リクエストが多すぎます |
Go でのエラーの返却
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *myServer) Greet(
ctx context.Context,
req *proto.GreetingRequest,
) (*proto.GreetingResponse, error) {
if req.Name == "" {
return nil, status.Error(
codes.InvalidArgument,
"name is required",
)
}
// より豊富なエラー詳細の場合:
// st, _ := status.New(codes.NotFound, "user not found").
// WithDetails(&errdetails.BadRequest{...})
// return nil, st.Err()
return &proto.GreetingResponse{
Message: "Hello " + req.Name,
}, nil
}
クライアントでのエラー処理
response, err := client.Greet(ctx, request)
if err != nil {
st, ok := status.FromError(err)
if ok {
switch st.Code() {
case codes.InvalidArgument:
log.Printf("Bad request: %s", st.Message())
case codes.Unavailable:
log.Println("Server unavailable, retrying...")
// 再試行ロジックを実装
default:
log.Printf("RPC error: code=%s msg=%s", st.Code(), st.Message())
}
}
return
}
🔒 TLS とセキュリティ
本番環境では、TLS なしで gRPC を実行しないでください。
TLS 付きサーバー
import "google.golang.org/grpc/credentials"
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil {
log.Fatalf("Failed to load TLS: %v", err)
}
grpcServer := grpc.NewServer(grpc.Creds(creds))
TLS 付きクライアント
creds, err := credentials.NewClientTLSFromFile("ca.crt", "")
if err != nil {
log.Fatalf("Failed to load TLS: %v", err)
}
conn, err := grpc.NewClient(
"myservice.example.com:443",
grpc.WithTransportCredentials(creds),
)
🩺 ヘルスチェックとグレースフルシャットダウン
gRPC ヘルスチェックプロトコル
gRPC は標準化されたヘルスチェックサービスを備えています。ロードバランサーおよびオーケストレーター(Kubernetes、ECS)がサービスをプローブできるように使用してください。
import "google.golang.org/grpc/health"
import healthpb "google.golang.org/grpc/health/grpc_health_v1"
grpcServer := grpc.NewServer()
healthServer := health.NewServer()
healthpb.RegisterHealthServer(grpcServer, healthServer)
// サービスステータスを設定
healthServer.SetServingStatus("myservice", healthpb.HealthCheckResponse_SERVING)
グレースフルシャットダウン
func main() {
grpcServer := grpc.NewServer()
// ... サービスを登録 ...
// OS シグナルを処理
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
go func() {
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}()
<-quit
log.Println("Shutting down gracefully...")
grpcServer.GracefulStop() // 停止前にインフライト RPC を完了
log.Println("Server stopped")
}
📊 本番環境チェックリスト
| 領域 | 実装すること |
| 可観測性 | 構造化ロギング(zap/slog)、Prometheus メトリクス、OpenTelemetry トレーシング |
| セキュリティ | サービス間 mTLS、トークンベース認証インターセプター、入力検証 |
| 復元力 | クライアント側再試行(バックオフ付き)、サーキットブレーカー、デッドラインの伝播 |
| ヘルス | gRPC ヘルスチェックプロトコル、Kubernetes readiness/liveness プローブ |
| バージョニング | protobuf フィールド番号を再利用または変更しないでください。新しいフィールドを追加し、古いものは非推奨にします |
| CI/CD | buf lint で .proto ファイルをリント、buf breaking で破壊的変更を検出 |
| デプロイ | Docker でコンテナ化、Kubernetes/ECS にデプロイ、サービスメッシュ(Istio/Linkerd)を使用 |
| テスト | ビジネスロジックを個別にユニットテスト、bufconn をインプロセス gRPC 統合テストに使用 |
🧪 bufconn を使用したテスト
bufconn を使用することで、実際の TCP ポートを開かずにインプロセスで gRPC サービスをテストできます:
import (
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024 * 1024
var lis *bufconn.Listener
func init() {
lis = bufconn.Listen(bufSize)
s := grpc.NewServer()
proto.RegisterGreetingServiceServer(s, &myGRPCServer{})
go func() {
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
}()
}
func bufDialer(context.Context, string) (net.Conn, error) {
return lis.Dial()
}
func TestGreet(t *testing.T) {
ctx := context.Background()
conn, err := grpc.NewClient(
"passthrough:///bufnet",
grpc.WithContextDialer(bufDialer),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
defer conn.Close()
client := proto.NewGreetingServiceClient(conn)
resp, err := client.Greet(ctx, &proto.GreetingRequest{Name: "Test"})
if err != nil {
t.Fatalf("Greet failed: %v", err)
}
if resp.Message != "Hello Test" {
t.Errorf("unexpected response: %s", resp.Message)
}
}
🌐 gRPC を REST として公開(gRPC-Gateway)
ブラウザクライアントまたは公開 API の場合は、gRPC-Gateway を使用して、.proto 定義から REST リバースプロキシを自動生成します:
import "google/api/annotations.proto";
service GreetingService {
rpc Greet(GreetingRequest) returns (GreetingResponse) {
option (google.api.http) = {
post: "/v1/greet"
body: "*"
};
}
}
これにより、protobuf に JSON を変換し、gRPC サーバーに転送し、レスポンスを JSON に変換する REST エンドポイント POST /v1/greet が生成されます。
🧰 有用なツール
| ツール | 目的 |
| Buf | .proto ファイルのリント、フォーマット、破壊的変更の検出 |
| grpcurl | gRPC 用 curl — CLI からサービスをテスト |
| Evans | インタラクティブ gRPC クライアント(REPL) |
| gRPC-Gateway | .proto から REST プロキシを自動生成 |
| go-grpc-middleware | 本番インターセプター(認証、ロギング、メトリクス、再試行) |
| Postman | API テスト用 gRPC サポート |
grpcurl でのクイック CLI テスト
# 利用可能なサービスを一覧表示(リフレクション有効化が必要)
grpcurl -plaintext localhost:8080 list
# RPC を呼び出し
grpcurl -plaintext -d '{"name": "John"}' localhost:8080 GreetingService/Greet
ツールがサービスを検出できるように、サーバーリフレクションを有効化してください:
import "google.golang.org/grpc/reflection"
grpcServer := grpc.NewServer()
proto.RegisterGreetingServiceServer(grpcServer, &myGRPCServer{})
reflection.Register(grpcServer) // リフレクションを有効化
📚 参考資料
最終更新:2026 年 3 月