これは GraphQL Advent Calendar 2019 14日目の記事です。
今回は私が趣味で作っているGraphQL Gatewayについての紹介になります。
gRPCのecosystemには、grpc-gatewayというJSONのREST-APIに変換するリバースプロキシサーバーが存在し、protocol bufferから自動生成することができます。
GraphQL Gatewayも同様にprotocol bufferからGraphQL用のリバースプロキシサーバーを自動生成して使用することができます。
Githubのリポジトリ
インストール方法
前提条件として、Golangでの開発環境に限ります。
protoc-gen-graphql-gateway
をインストールする
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
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
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サーバーを生成します。
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のサーバを実装します。
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というクライアントを使用してアクセスしてみます。)
画像でも分かる通り2つのデータが取得出来ているのが確認できます。
実際はUserService
とBookService
の異なるgRPCサーバにアクセスして1つのレスポンスとしてフロントに返すことが出来ています。
まとめ
今回、趣味で作っているgraphql-gatewayについて紹介しました。
現在はQueryとMutationまでしか実装出来ていないため、Subscriptionも対応したり、CDNでも利用できるようなキャッシュ機構を付けたり、Apolloなどのクライアントにも対応したりと、やりたいことが沢山あるので粛々とやっていきたいと思います。
(Githubの方にスター等を頂けますと今後の開発のやる気にも繋がるので宜しくおねがいします。)