grpc-gatewayを利用したREST APIの提供がすごく便利な件

#Go

# gRPC

REST APIを提供するとき、APIのスキーマ定義をどのように管理するか悩むことがある。 OpenAPI(Swagger)で定義してモデルを吐き出すこともあれば、コードとは別にMarkdownで書き出すこともあるだろう。 マイクロサービス構成をとった場合にはサービス間の通信にgRPCを採用している場合もあり、REST APIはSwagger、gRPCはprotobufなどスキーマ定義の管理にかかる複雑さは増すばかりである。

このような状況を解決できそうな方法があり、すごく気に入ったので紹介する。

grpc-gateway

grpc-gatewayはgRPCで提供されているAPIをJSON APIに変換して提供するためのコードを生成するツールである。 Protocol Buffers定義からコードを生成するためのプラグインの1つとして提供されており、bufなどを利用してシュッと導入できる。 grpc-gatewayで生成されたコードはプロキシの役目を担うため、gRPC APIを提供するサーバの他にプロキシサーバを動かすことで提供が可能になる。

実際に試してみた

Protocol Buffers定義を以下のようにした。 リクエストボディに入っている文字列をそのままレスポンスに返すような形式とし、REST APIのエンドポイントを/v1/echoとした。

api.proto
syntax = "proto3";

package grpc_gateway_practice.v1;
option go_package = "github.com/Pranc1ngPegasus/grpc-gateway-practice/proto/grpc_gateway_practice/v1";

import "google/api/annotations.proto";

service GrpcGatewayPracticeService {
  rpc Echo(EchoRequest) returns (EchoResponse) {
    option (google.api.http) = {
      post : "/v1/echo"
      body : "*"
    };
  }
}

message EchoRequest { string value = 1; }

message EchoResponse { string value = 1; }

grpc-gatewayを利用してOpenAPI定義ファイルを出力した様子が以下である。 /v1/echoにPOSTでAPIが切られていることがわかる。

api.swagger.json
{
  "swagger": "2.0",
  "info": {
    "title": "grpc_gateway_practice/v1/api.proto",
    "version": "version not set"
  },
  "tags": [
    {
      "name": "GrpcGatewayPracticeService"
    }
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/v1/echo": {
      "post": {
        "operationId": "Echo",
        "responses": {
          "200": {
            "description": "A successful response.",
            "schema": {
              "$ref": "#/definitions/v1EchoResponse"
            }
          }
        },
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/v1EchoRequest"
            }
          }
        ],
        "tags": [
          "GrpcGatewayPracticeService"
        ]
      }
    }
  },
  "definitions": {
    "v1EchoRequest": {
      "type": "object",
      "properties": {
        "value": {
          "type": "string"
        }
      }
    },
    "v1EchoResponse": {
      "type": "object",
      "properties": {
        "value": {
          "type": "string"
        }
      }
    }
  }
}

どう便利なのか

APIスキーマ定義

REST API用の定義もgRPC用の定義も全てProtocol Buffersで定義できる。 Protocol Buffersの記述はコード量が少なく済むうえ、複数ファイルなどに分けて管理することで見通しがよくなる。

コード生成

gRPC APIのスタブはprotoc-gen-goなどを利用すれば吐き出すことができる。 REST APIのプロキシはgrpc-gatewayが吐き出してくれるうえ、プロシキするためのコードは全て生成されるのでHTTPサーバを起動するコードだけ自前で実装すれば動作してくれる。

APIドキュメント管理

gRPC APIのドキュメントはprotoc-gen-docなどを利用すれば吐き出すことができる。 問題はREST APIのドキュメントであるが、grpc-gatewayに内包されているprotoc-gen-openapiv2を利用すればOpenAPIの定義ファイルを吐き出すことができるので、そのままSwagger-UIやReDocなどを利用してホストすることが可能である。

以下に今回生成されたOpenAPIファイルをReDocで表示してみた。

まとめ

gRPCで通信するマイクロサービス群と、HTTPで通信するWebフロントやモバイルアプリなどを共存させるときにサーバサイドのAPI定義やコード管理にかかる労力を大きく減らせる技術だと感じた。