メインコンテンツまでスキップ
最新1mo ago

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.1HTTP/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/binPATH に含まれていることを確認してください:

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=protogRPC サービススタブ(greeting_grpc.pb.go)を proto/ ディレクトリに生成

これにより 2 つのファイルが生成されます:

  • greeting.pb.goGreetingRequestGreetingResponse 用の Go 構造体、加えてシリアライゼーションメソッド

  • greeting_grpc.pb.goGreetingServiceServer インターフェース(これを実装)と 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)
}
}

何が起こっているか

  1. 自動生成された UnimplementedGreetingServiceServer埋め込む構造体(myGRPCServer)を定義します。これは前方互換性パターンです — .proto ファイルに新しい RPC が追加されると、コードは引き続きコンパイルされます(未実装メソッドはデフォルトで「Unimplemented」エラーを返します)。

  2. 実際のビジネスロジックで Greet メソッドを実装します。

  3. 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.Dialgrpc.WithInsecure() は非推奨です。最新の Go gRPC バージョンでは、grpc.NewClientgrpc.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 つのモード):

UnaryStreaming
ServerUnaryServerInterceptorStreamServerInterceptor
ClientUnaryClientInterceptorStreamClientInterceptor

ロギングインターセプターの例

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 パッケージは本番環境対応のインターセプターを提供します:

インターセプター目的
authAuthFunc 経由のカスタム認証ロジック
recoveryパニックをキャッチして gRPC エラーに変換
loggingzap、logrus、または slog による構造化ロギング
prometheusクライアントおよびサーバーメトリクス
retryバックオフ付き自動クライアント側再試行
validatorprotobuf 定義から受信メッセージを自動検証
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/CDbuf 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 ファイルのリント、フォーマット、破壊的変更の検出
grpcurlgRPC 用 curl — CLI からサービスをテスト
Evansインタラクティブ gRPC クライアント(REPL)
gRPC-Gateway.proto から REST プロキシを自動生成
go-grpc-middleware本番インターセプター(認証、ロギング、メトリクス、再試行)
PostmanAPI テスト用 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 月