mcpField
G
golang-grpc

golang-grpc

github.com/samber開発3.7k39.6k 回インストール5 日前に更新
リポジトリ

golang-grpc について

Go エンジニア向けの gRPC スキルです。サービスの実装とレビューを支援し、正確性・セキュリティ・運用性を扱います。

主な機能

  1. 01実装・レビューのモード
  2. 02Proto の構成とコード生成
  3. 03ステータスコードによるエラー処理
  4. 04期限設定と接続再利用
  5. 05インターセプター、ヘルスチェック、終了処理
  6. 06ストリーミング、セキュリティ、テストの指針

活用シーン

  1. 01Go gRPC サーバーやクライアントの実装
  2. 02RPC のエラー処理とセキュリティの監査
  3. 03bufconn を使ったサービスのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-grpc

インストール後、Claude Code で/golang-grpcを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
protoc-reference.md
testing.md

詳細

ペルソナ: Go 分散システムエンジニアです。正確性と運用性を考慮して gRPC サービスを設計します。適切なステータスコード、デッドライン、インターセプター、グレースフルシャットダウンは、正常系と同じくらい重要です。

モード:

  • 構築モード — gRPC サーバーまたはクライアントをゼロから実装します。
  • レビュー モード — 既存の gRPC コードを監査し、正確性、セキュリティ、運用性の問題を確認します。

依存関係:

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

Go gRPC のベストプラクティス

gRPC は純粋なトランスポート層として扱い、ビジネスロジックとは分離してください。公式の Go 実装は google.golang.org/grpc です。

このスキルは網羅的なものではありません。詳細については、ライブラリのドキュメントやコード例を参照してください。

  • Go パッケージのドキュメント、シンボル、バージョン、インポーター、既知の脆弱性については、Context7 よりも samber/cc-skills-golang@golang-pkg-go-dev スキル(godig)を優先してください。
  • 独自コード内でのこのライブラリの使用状況(定義、呼び出し箇所、診断情報)を調べるには、samber/cc-skills-golang@golang-gopls スキル(gopls)を参照してください。
  • Context7 は、pkg.go.dev にインデックスされていないドキュメントのフォールバックとして引き続き利用できます。

クイックリファレンス

懸念事項パッケージ/ツール
サービス定義.proto ファイルと protoc または buf
コード生成protoc-gen-go、protoc-gen-go-grpc
エラー処理codes と google.golang.org/grpc/status
詳細なエラー情報google.golang.org/genproto/googleapis/rpc/errdetails
インターセプターgrpc.ChainUnaryInterceptor、grpc.ChainStreamInterceptor
ミドルウェアエコシステムgithub.com/grpc-ecosystem/go-grpc-middleware
テストgoogle.golang.org/grpc/test/bufconn
TLS/mTLSgoogle.golang.org/grpc/credentials
ヘルスチェックgoogle.golang.org/grpc/health

Proto ファイルの構成

ドメインごとに、バージョン付きのディレクトリ(proto/user/v1/)で構成してください。必ず Request/Response ラッパーメッセージを使用してください。string のような基本型には、後からフィールドを追加できません。buf generate または protoc で生成します。

Proto とコード生成のリファレンス

サーバーの実装

  • ヘルスチェックサービス(grpc_health_v1)を実装してください。Kubernetes のプローブがレディネスを判定するために必要です。
  • 横断的関心事(ロギング、認証、リカバリー)にはインターセプターを使用してください。ビジネスロジックをクリーンに保てます。
  • タイムアウト時に Stop() にフォールバックする GracefulStop() を使用してください。これにより、処理中の RPC を完了させながらハングを防止できます。
  • 本番環境ではリフレクションを無効にしてください。API の全体像が公開されます。
srv := grpc.NewServer(
    grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, health.NewServer())

go srv.Serve(lis)

// On shutdown signal:
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
    srv.Stop()
}
インターセプターパターン
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    start := time.Now()
    resp, err := handler(ctx, req)
    log.Printf("method=%s duration=%s code=%s", info.FullMethod, time.Since(start), status.Code(err))
    return resp, err
}

クライアントの実装

  • 接続を再利用してください。gRPC は単一の HTTP/2 接続で RPC を多重化するため、リクエストごとに接続すると TCP/TLS ハンドシェイクが無駄になります。
  • すべての呼び出しにデッドライン(context.WithTimeout)を設定してください。設定しないと、低速な上流によってゴルーチンが無期限に停止します。
  • dns:/// スキーム経由で Kubernetes のヘッドレスサービスを使用する場合は、round_robin を使用してください。
  • metadata.NewOutgoingContext を使ってメタデータ(認証トークン、トレース ID)を渡してください。
conn, err := grpc.NewClient("dns:///user-service:50051",
    grpc.WithTransportCredentials(creds),
    grpc.WithDefaultServiceConfig(`{
        "loadBalancingPolicy": "round_robin",
        "methodConfig": [{
            "name": [{"service": ""}],
            "timeout": "5s",
            "retryPolicy": {
                "maxAttempts": 3,
                "initialBackoff": "0.1s",
                "maxBackoff": "1s",
                "backoffMultiplier": 2,
                "retryableStatusCodes": ["UNAVAILABLE"]
            }
        }]
    }`),
)
client := pb.NewUserServiceClient(conn)

エラー処理

gRPC エラーは、必ず具体的なコードを指定して status.Error で返してください。生の error は codes.Unknown となり、クライアントが対処方法を判断できません。クライアントはコードに基づいて、再試行するか、即座に失敗させるか、機能を縮退させるかを判断します。

コード使用する場面
InvalidArgument入力形式が不正(フィールドの不足、形式の不正)
NotFoundエンティティが存在しない
AlreadyExists作成に失敗したが、エンティティは存在する
PermissionDenied呼び出し元に権限がない
Unauthenticatedトークンがない、または無効
FailedPreconditionシステムが必要な状態ではない
ResourceExhaustedレート制限またはクォータを超過
Unavailable一時的な問題で、安全に再試行できる
Internal予期しないバグ
DeadlineExceededタイムアウト
// ✗ Bad — caller gets codes.Unknown, can't decide whether to retry
return nil, fmt.Errorf("user not found")

// ✓ Good — specific code lets clients act appropriately
if errors.Is(err, ErrNotFound) {
    return nil, status.Errorf(codes.NotFound, "user %q not found", req.UserId)
}
return nil, status.Errorf(codes.Internal, "lookup failed: %v", err)

フィールド単位の検証エラーには、status.WithDetails を使って errdetails.BadRequest を付加してください。

ストリーミング

パターンユースケース
サーバーストリーミングサーバーが一連のデータを送信する(ログの追跡、結果セット)
クライアントストリーミングクライアントが一連のデータを送信し、サーバーが一度だけ応答する(ファイルのアップロード、バッチ処理)
双方向双方が独立して送信する(チャット、リアルタイム同期)

大きな単一メッセージよりもストリーミングを優先してください。メッセージごとのサイズ制限を回避し、メモリ負荷を軽減できます。

func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
    for _, u := range users {
        if err := stream.Send(u); err != nil {
            return err
        }
    }
    return nil
}

テスト

bufconn を使うと、ネットワークのオーバーヘッドなしに、gRPC スタック全体(シリアライズ、インターセプター、メタデータ)を通すインメモリー接続をテストできます。エラーシナリオで期待される gRPC ステータスコードが返ることを必ずテストしてください。

テストのパターンと例

セキュリティ

  • 本番環境では TLS を必ず有効にしてください。認証情報はメタデータ内を転送されます
  • サービス間認証には mTLS を使用するか、サービスメッシュ(Istio、Linkerd)に委任してください
  • ユーザー認証には credentials.PerRPCCredentials を実装し、認証インターセプターでトークンを検証してください
  • API の検出を防ぐため、本番環境ではリフレクションを無効にすることが望ましいです

パフォーマンス

設定目的一般的な値
keepalive.ServerParameters.Timeアイドル接続への ping 間隔30s
keepalive.ServerParameters.Timeoutping の応答タイムアウト10s
grpc.MaxRecvMsgSize大きなペイロード向けにデフォルトの 4 MB を上書き16 MB
接続プール高負荷のストリーミング向けに複数の接続を使用4 connections

ほとんどのサービスでは接続プールは不要です。複雑さを増す前にプロファイリングしてください。

よくある間違い

間違い修正方法
生の error を返すcodes.Unknown となり、クライアントは再試行すべきか判断できません。具体的なコードを指定して status.Errorf を使用してください
クライアント呼び出しにデッドラインがない上流の処理が遅いと、いつまでも応答を待ち続けます。必ず context.WithTimeout を使用してください
リクエストごとに新しい接続を作成するTCP/TLS ハンドシェイクが無駄になります。接続は一度作成して再利用してください。HTTP/2 は RPC を多重化します
本番環境でリフレクションを有効にする攻撃者がすべてのメソッドを列挙できてしまいます。dev/staging でのみ有効にしてください
すべてのエラーに codes.Internal を使うコードが誤っていると、クライアントの再試行ロジックが機能しません。Unavailable は再試行を促しますが、InvalidArgument は促しません
RPC の引数にラッパーのない型を使うstring にフィールドを追加することはできません。ラッパーメッセージを使うと、後方互換性を保ちながら進化させられます
ヘルスチェックサービスがないKubernetes が準備状態を判断できず、デプロイ中に Pod を終了させます
コンテキストのキャンセルを無視する呼び出し元が処理を諦めた後も、長時間の処理が続きます。ctx.Err() を確認してください

相互参照

  • → デッドラインとキャンセルのパターンについては、samber/cc-skills-golang@golang-context スキルを参照してください
  • → gRPC エラーから Go エラーへのマッピングについては、samber/cc-skills-golang@golang-error-handling スキルを参照してください
  • → gRPC インターセプター(ロギング、トレース、メトリクス)については、samber/cc-skills-golang@golang-observability スキルを参照してください
  • → bufconn を使った gRPC のテストについては、samber/cc-skills-golang@golang-testing スキルを参照してください

コメント