gRPCマイクロサービス用のgraphql-gatewayサーバーの紹介

これは [GraphQL Advent Calendar 2019](https://qiita.com/advent-calendar/2019/graphql) 14日目の記事です。

今回は私が趣味で作っているGraphQL Gatewayについての紹介になります。
gRPCのecosystemには、[grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)というJSONのREST-APIに変換するリバースプロキシサーバーが存在し、protocol bufferから自動生成することができます。
GraphQL Gatewayも同様にprotocol bufferからGraphQL用のリバースプロキシサーバーを自動生成して使用することができます。

Githubのリポジトリ

https://github.com/grpc-custom/graphql-gateway


## インストール方法

前提条件として、Golangでの開発環境に限ります。

`protoc-gen-graphql-gateway`をインストールする

```bash
go get -u github.com/grpc-custom/graphql-gateway/cmd/protoc-gen-graphql-gateway
go get -u github.com/golang/protobuf/protoc-gen-go
```

`protoc-gen-go`はgRPCを使用するため必要になります。(既にインストール済の場合は必要ありません。)

## 使い方

今回はサンプルとして、`UserService`と`BookService`を作り、`UserService`ではIDを指定してユーザデータが取得できるユーザ機能と`BookService`ではISBNを指定して本データを取得できるマスター機能を作りたいと思います。

### 1. protoを定義する

`user.proto`

```proto
syntax = "proto3";
package user;

import "github.com/grpc-custom/graphql-gateway/graphql.proto";

message User {
  int32 id    = 1;
  string name = 2;
}

message GetUserRequest {
  int32 id = 1;
}

message GetUserResponse {
  User user = 1;
}

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse) {
    // graphql-gatewayのカスタムオプションを指定する
    option (grpc_custom.graphql.schema) = {
      query: "getUser" // GraphQLのクエリー文字指定
    };
  }
}
```

`BookService`

```proto
syntax = "proto3";
package book;

import "github.com/grpc-custom/graphql-gateway/graphql.proto";

message Book {
  string isbn  = 1;
  string title = 2;
}

message GetBookRequest {
  string isbn = 1;
}

message GetBookResponse {
  Book book = 1;
}

service BookService {
  rpc GetBook(GetBookRequest) returns (GetBookResponse) {
    option (grpc_custom.graphql.schema) = {
      query: "getBook"
    };
  }
}
```

### 2. reverse-proxyを生成する

`protoc`コマンドでGraphQLサーバーを生成します。

```bash
protoc \
  -I=${GOPATH}/src:. \
  --go_out=plugins=grpc:. \
  --graphql-gateway_out=. \
  /path/to/user.proto
protoc \
  -I=${GOPATH}/src:. \
  --go_out=plugins=grpc:. \
  --graphql-gateway_out=. \
  /path/to/book.proto
```

`*.gql.go`というファイル名で生成されます。

### 3. サーバコードを実装する

2で生成したGraphQLを使用してGolangのサーバを実装します。

```go
package main

import (
	"context"
	"log"
	"net/http"

	"github.com/grpc-custom/graphql-gateway/runtime"
	"google.golang.org/grpc"

	"path/to/book" // protocで生成されたファイルをimport
	"path/to/user"
)

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	mux, err := runtime.NewServeMux()
	if err != nil {
		log.Fatal(err)
	}
	opts := []grpc.DialOption{
		grpc.WithInsecure(),
	}
	// gRPCのUserServiceにアクセスするための定義を追加
	err = user.RegisterUserServiceFromEndpoint(ctx, mux, "localhost:9001", opts)
	if err != nil {
		log.Fatal(err)
	}
	// gRPCのBookServiceにアクセスするための定義を追加
	err = book.RegisterBookServiceFromEndpoint(ctx, mux, "localhost:9002", opts)
	if err != nil {
		log.Fatal(err)
	}
	err = http.ListenAndServe(":8080", mux)
	if err != nil {
		log.Fatal(err)
	}
}
```

## 動作確認

`UserService`と`BookService`のgRPCサーバを起動し、先程作成したGraphQLサーバも起動させて実際にアクセスしてみます。

エンドポイントは`/graphql`というのが固定で出来ていますので、そこに向けてアクセスしてみます。

(今回は[Insomnia](https://insomnia.rest/)というクライアントを使用してアクセスしてみます。)

![sample](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5481/5b180f79-6f19-e561-f8a2-e74fa57f354f.png)

画像でも分かる通り2つのデータが取得出来ているのが確認できます。

実際は`UserService`と`BookService`の異なるgRPCサーバにアクセスして1つのレスポンスとしてフロントに返すことが出来ています。

## まとめ

今回、趣味で作っているgraphql-gatewayについて紹介しました。

現在はQueryとMutationまでしか実装出来ていないため、Subscriptionも対応したり、CDNでも利用できるようなキャッシュ機構を付けたり、Apolloなどのクライアントにも対応したりと、やりたいことが沢山あるので粛々とやっていきたいと思います。

(Githubの方にスター等を頂けますと今後の開発のやる気にも繋がるので宜しくおねがいします。)