gRPCとRESTを自動で連携させるgRPC-Gateway
Ethan Miller
Product Engineer · Leapcell

はじめに
現代のマイクロサービスアーキテクチャにおいて、gRPCとRESTのどちらを選択するかはしばしばジレンマとなります。gRPCは、その高性能、強力な型付け、効率的なバイナリシリアライゼーション(Protocol Buffers)により、分散システム内でのサービス間通信の事実上の標準となっています。しかし、Webブラウザ、モバイルアプリケーション、サードパーティインテグレーターなどの外部クライアントにサービスを公開する場合、RESTful APIはそのシンプルさ、広範な採用、優れたツールサポートにより、依然として遍在しています。同じ基盤となるサービスに対して、1つはgRPC、もう1つはRESTという2つの別個のAPIを手動で開発・保守することは、複雑さ、冗長性、開発オーバーヘッドの大きな原因となり得ます。ここでgRPC-Gatewayが輝きを放ちます。これは、gRPCサービスを正規のRESTful APIとして自動的に公開するエレガントなソリューションを提供し、デュアル実装の負担なしに両方のパラダイムの利点を活用できるようにします。この記事では、gRPC-Gatewayがどのようにこのシームレスな統合を実現し、開発者が効率的で保守可能でアクセスしやすいサービスを構築できるようにするかを掘り下げます。
コアコンセプトと実装
gRPC-Gatewayの具体的な内容に入る前に、その動作を支えるいくつかのコアコンセプトを簡単に定義しましょう。
gRPC: Googleによって開発された、高性能なオープンソースのユニバーサルRPCフレームワークです。Interface Definition Language(IDL)およびメッセージシリアライゼーションにはProtocol Buffersを、トランスポートにはHTTP/2を使用します。これにより、ストリーミング、双方向ストリーミング、強力な型チェックなどの機能を持つ効率的な通信が可能になります。
Protocol Buffers (Protobuf): 言語非依存、プラットフォーム非依存で拡張可能な構造化データのシリアライゼーションメカニズムです。データ構造を一度定義すれば、生成されたソースコードを使用して、さまざまなデータストリームとの間で構造化データを簡単に読み書きできます。
RESTful API: ネットワークアプリケーションを設計するためのアーキテクチャスタイルです。ステートレスなクライアント-サーバー通信モデルに依存し、標準的なHTTPメソッド(GET、POST、PUT、DELETE)を使用し、通常はJSONまたはXML形式でリソース表現を返します。
gRPC-Gateway: RESTful HTTP/JSONリクエストをgRPCリクエストに変換するリバースプロキシです。gRPCサービス定義(HTTPルールなどの特別なアノテーションを含む)を読み取り、受信したRESTリクエストを対応するgRPCサーバーに転送するリバースプロキシサーバーを生成します。ここでの鍵は「自動生成」であり、手動コーディングを大幅に削減します。
gRPC-Gatewayの仕組み
gRPC-Gatewayの魔法は、Protobufサービス定義を内部的に調査し、ブリッジとして機能するGoコードを生成する能力にあります。このプロセスは通常、以下のステップを含みます。
-
HTTPアノテーションでgRPCサービスを定義する: Protocol Buffersを使用して、サービスとメッセージ構造を定義することから始めます。RESTfulエンドポイントとして公開したいメソッドについては、Protobuf定義内に特別な
google.api.http
オプションを追加します。これらのアノテーションは、HTTPメソッド(GET、POSTなど)、URLパス、およびリクエストパラメータがProtobufフィールドにどのようにマップされるかを指定します。 -
gRPCおよびgRPC-Gatewayコードを生成する: Protocol Bufferコンパイラである
protoc
と、特定のプラグイン(gRPCにはprotoc-gen-go
、gRPC-Gatewayにはprotoc-gen-grpc-gateway
)を使用して、必要なGoコードを生成します。protoc-gen-grpc-gateway
プラグインは、HTTPハンドラーロジックを含む*.pb.gw.go
ファイルを各サービスごとに作成します。 -
gRPCサーバーを実行する: gRPCサービスは、
.proto
ファイルで定義されたロジックを実装し、通常は特定のポートでgRPCリクエストをリッスンします。 -
gRPC-Gatewayプロキシを実行する: 生成されたgRPC-Gatewayハンドラーをインスタンス化する別のGoアプリケーションを作成します。このゲートウェイアプリケーションはHTTPサーバーとして機能し、受信したRESTful HTTP/JSONリクエストをリッスンします。その後、これらのリクエストをgRPC呼び出しに変換し、gRPCサーバーに転送します。gRPCサーバーからの応答は、JSONに再度変換されてクライアントに送信されます。
コード例
簡単な「Greeter」サービスでこれを説明しましょう。
1. greeter.proto
:
syntax = "proto3"; package greeter; import "google/api/annotations.proto"; option go_package = "./greeter"; service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse) { option (google.api.http) = { get: "/v1/hello/{name}" }; } rpc SayHelloPost (HelloRequest) returns (HelloResponse) { option (google.api.http) = { post: "/v1/hello_post" body: "*" }; } } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
option (google.api.http)
の行に注意してください。これらのアノテーションはgRPC-Gatewayにとって極めて重要です。
GET: "/v1/hello/{name}"
は、/v1/hello/John
へのGETリクエストをSayHello
メソッドにマッピングし、「John」をHelloRequest
のname
フィールドに抽出します。POST: "/v1/hello_post"
とbody: "*"
は、/v1/hello_post
へのPOSTリクエストで{"name": "Alice"}
のようなJSONボディをSayHelloPost
メソッドにマッピングします。
2. Goコードの生成:
protoc
コンパイラとprotoc-gen-go
、protoc-gen-grpc-gateway
プラグインをインストールする必要があります。
# protoc-gen-goをインストール go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest # gRPCサービス生成用 # protoc-gen-grpc-gatewayをインストール go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest # オプション、OpenAPI仕様生成用 # アノテーションで利用できるようにProtobuf定義を準備 # googleapis/google/apiをクローンまたはコピーしてプロトパスに配置する必要がある場合があります # 例: プロトのルートに'google'フォルダを作成し、'api'をその中に配置します。 # または、bufのようなツールを使用して依存関係を管理します。 protoc -I. \ -I/usr/local/include \ -I$(go env GOPATH)/src \ -I$(go env GOPATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ --grpc-gateway_out=. --grpc-gateway_opt=paths=source_relative \ greeter.proto
このコマンドは、greeter.pb.go
(Protobufメッセージ)、greeter_grpc.pb.go
(gRPCサービスインターフェースとクライアント)、およびgreeter.pb.gw.go
(gRPC-Gatewayハンドラー)を生成します。
3. gRPCサーバーの実装(サーバー用main.go
):
package main import ( "context" "fmt" "log" "net" "google.golang.org/grpc" pb "your_module_path/greeter" // your_module_pathを実際のGoモジュールパスに置き換えてください ) type server struct { pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloResponse{Message: "Hello " + in.GetName()}, } func (s *server) SayHelloPost(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { log.Printf("Received via POST: %v", in.GetName()) return &pb.HelloResponse{Message: "Hello from POST, " + in.GetName()}, } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) log.Printf("gRPC server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
4. gRPC-Gatewayプロキシの実行(ゲートウェイ用main.go
、同じまたは別のプロジェクトに配置可能):
package main import ( "context" "log" "net/http" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "your_module_path/greeter" // your_module_pathを置き換えてください ) func main() { ictx := context.Background() ictx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} // TLSを使用する場合は、上記をgrpc.WithTransportCredentialsに置き換えてください // Greeterサービスを登録 err := pb.RegisterGreeterHandlerFromEndpoint(ctx, mux, ":50051", opts) if err != nil { log.Fatalf("Failed to register gateway: %v", err) } log.Printf("gRPC-Gateway server listening on :8080") if err := http.ListenAndServe(":8080", mux); err != nil { log.Fatalf("Failed to serve gateway: %v", err) } }
これで、gRPCサーバーとゲートウェイサーバーを実行できます。その後、RESTfulリクエストを行うことができます。
# GETリクエスト curl http://localhost:8080/v1/hello/World # 期待される出力: {"message":"Hello World"} # POSTリクエスト curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice"}' http://localhost:8080/v1/hello_post # 期待される出力: {"message":"Hello from POST, Alice"}
アプリケーションシナリオ
gRPC-Gatewayは、特にさまざまなシナリオで有益です。
- ハイブリッド環境: 内部gRPCサービスを、RESTを好むか必要とする外部クライアント(例:フロントエンドWebアプリケーション、モバイルアプリ、サードパーティAPI)に公開する必要がある場合。
- 段階的移行: 既存のRESTful APIをgRPCに移行している場合、gRPC-Gatewayを使用すると、既存の外部クライアントとの互換性を維持しながら、内部でgRPCを導入できます。
- クライアント開発の簡素化: ネイティブgRPCクライアントライブラリがすぐに利用できないか、統合が複雑な環境(例:クライアントサイドJavaScript、テスト用のシンプルなcurlリクエスト)の場合。
- APIプロトタイピングとドキュメント:
protoc-gen-openapiv2
を使用すると、gRPC-GatewayはProtobuf定義とHTTPアノテーションから直接OpenAPI(Swagger)仕様を生成でき、APIドキュメントを効率化できます。
結論
gRPC-Gatewayは、内部通信のためのgRPCのパフォーマンス上の利点と、外部クライアントのためのRESTの広範なアクセス可能性をどのように調和させるかという、一般的なアーキテクチャ上の課題に対する強力でエレガントなソリューションを提供します。Protocol Bufferアノテーションとコード生成を活用することで、手動でのデュアルAPI実装の必要性を完全に排除し、開発工数を大幅に節約し、一貫性の可能性を減らします。このアプローチは、開発者の生産性を向上させるだけでなく、システム全体で一貫したサービス定義を促進し、gRPCとRESTの両方の利点を真に最大化できるようにします。