何のため?
前提
- 目的は、一つのAPIで複数データベースの検索や操作。
- 複数のデータベースがすでにあり、それぞれにGraphQL APIがある(一部はREST APIでもよい)。これを以降、生APIと呼ぶ。
やりたいこと
- 生APIはそのままでは使いづらいため、使いやすい新しいAPIを作りたい。
- ただし、フルスクラッチで作りたくはない。スキーマなどはコピペで楽したい。
Schema Stitching (スキーマスティッチング)とは
Schema stitching (スキーマスティッチング)とは、「スキーマのつぎはぎ」。
以下のようなメリットがある。
-
既存の複数のGraphQL APIからスキーマをコピペするように取り込むことができる (
makeRemoteExecutableSchema
) -
複数スキーマをつぎはぎし、新しいAPIスキーマを自動作成する (
mergeSchema
)
さっそくやってみる
既存のGraphQL APIとして下記がある。
-
users
:ユーザ情報 -
locations
:位置情報 -
users
のaddress
フィールドがlocations
のaddress
フィールドとJOIN可能。
users | locations |
---|---|
スキーマスティッチングを下記のように実施する。
-
users
のスキーマの中に新フィールドlocations
を用意。 - 新フィールド
locations
は、users
のaddress
と同じaddress
を持ったlocations
の配列。
Schema stitchingのソースコード
-
users
,locations
は個別にGraphQLServerを準備
スティッチング用BFFサーバでは、以下を実施する。
-
- 上記既存APIをリモートスキーマとして読み込む
- リモートスキーマ読み込みには、
graphql-tools
のmakeRemoteExecutableSchema
,introspectSchema
を使う
-
- 既存スキーマにないフィールド
users.locations
を追加するスキーマ差分linkSchemaDef
を作成
- 既存スキーマにないフィールド
-
- スキーマスティッチング(スキーマのマージ)
mergeSchemas
を活用
- 既存スキーマ+スキーマ差分を代入
- 追加フィールドの内部実装(resolve)を
delegateToSchema
を使い記述
- スキーマスティッチング(スキーマのマージ)
import { GraphQLServer } from 'graphql-yoga'
import { makeRemoteExecutableSchema, mergeSchemas, introspectSchema } from 'graphql-tools'
import { createHttpLink } from 'apollo-link-http'
import { Binding } from 'graphql-binding'
import fetch from 'node-fetch'
import { config } from 'dotenv'
config()
const __API_PORT__ = process.env.API_PORT
async function run() {
// 1. remote schema
const createRemoteSchema = async (uri: string) => {
const link = createHttpLink({uri, fetch})
return makeRemoteExecutableSchema({
schema: await introspectSchema(link),
link,
});
}
const userSchema = await createRemoteSchema('http://localhost:4020')
const locationSchema = await createRemoteSchema('http://localhost:4021')
// 2. define linkSchemaDefs
const linkSchemaDefs = `
extend type User {
locations: [Location],
}
`
// 3. merge
const schema = mergeSchemas({
schemas: [userSchema, locationSchema, linkSchemaDefs],
resolvers: {
User: {
locations: {
fragment: `fragment UserFragment on User {address}`,
resolve: async (parent: any, args: any, context: any, info: any) => {
return info.mergeInfo.delegateToSchema({
schema: locationSchema,
operation: 'query',
fieldName: 'locations',
args: {where: {address: parent.address}},
context,
info
})
}
},
}
}
})
const server = new GraphQLServer({ schema })
server.start({port: __API_PORT__}, () =>
console.log(`Your GraphQL server is running now ...`),
)
}
run()
結果
users
スキーマにlocations
というフィールドが追加されており、locations
APIの結果がjoinされていることがわかる。
気を付けるところ
-
address
カラムでjoinの実装はargs
にlocations
クエリの引数を入れることで実現する。 -
args: {where: {address: parent.address}}
とする - これで
locations( {where: {address: <usersのaddress> }} )
というクエリが発行される
ちょっとスキーマをアレンジする、リクエストをバッチ化する
次回につづく。。。
実際のソースコード
参考
https://www.prisma.io/blog/how-do-graphql-remote-schemas-work-7118237c89d7/
https://www.apollographql.com/docs/graphql-tools/schema-delegation.html
https://www.apollographql.com/docs/graphql-tools/schema-stitching.html