何のため?
前回Schema Stitching: 複数GraphQL APIをくっつけて新しいAPIを作ろうにて、スキーマスティッチングの基本・最低限の実装をしたわけですが、最低限実装では自由度が低くてなかなか使い物になりません。
自由度が低いところ
- 型の変更が難しい
- 既存APIの出力が配列型だとスティッチング結果もそのまま配列型になってしまう
- スキーマの変更が難しい
-
delegateToSchema
を単純に使うだけでは既存APIと同じスキーマになってしまう
-
graphql-bindingでちょっとカスタマイズしてみる
非常に簡単にできます。
実装方法概要
- 定義の変更は**スキーマで
extend
**を使う - 出力処理の変更はBindingかSchemaTransformを使う(今回はBindingを活用)
-
location
APIのスキーマをBindingに入力してbindingオブジェクトを作成 - bindingオブジェクトの**
query
を呼び出し、取得locations
オブジェクトを直接操作**
-
実装
ソースコードは、前回実装したものに一部追加する。
-
型を変える
- 【変更前】既存API
locations
の型が[Location]
という配列
→【変更後】新APIではその要素1つを取り出し、型をLocation
とする
- 【変更前】既存API
-
スキーマを変える
-
Location
スキーマに新フィールドlocation_type
を追加する
-
users
の内部を以下のように変更する。
- 旧フィールド
locations
をスキーマ変更した新フィールドlocation
を作る -
location
は、locations
の配列から0番目の要素を取り出して表示する - さらに既存
locations
APIにはない新フィールドlocation_type
を追加し、値を"LARGE_CITY"
を入力する
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() {
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')
const linkSchemaDefs = `
extend type User {
# original
locations: [Location],
# new api (TYPE CHANGE: from array to single object) ★追加
location: Location
}
# ★追加
extend type Location {
# SCHEMA CHANGE: field added
location_type: String
}
`
// ★★追加
class LocationBinding extends Binding {
constructor() {
super({ schema: locationSchema })
}
}
const locationBinding = new LocationBinding()
const schema = mergeSchemas({
schemas: [userSchema, locationSchema, linkSchemaDefs],
resolvers: {
User: {
// original
locations: {
fragment: `fragment LocationFragment on Location {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
})
}
},
// new api ★★追加
location: {
fragment: `fragment LocationFragment on Location {address}`,
resolve: async (parent: any, args: any, context: any, info: any) => {
let locations = await locationBinding.query.locations({where: {address: parent.address}}, info)
return {
...locations[0],
// add new field
location_type: "LARGE-CITY"
}
}
}
}
}
})
const server = new GraphQLServer({ schema })
server.start({port: __API_PORT__}, () =>
console.log(`Your GraphQL server is running now ...`),
)
}
run()
結果
users
に対してクエリを打った結果。location
を含んでおり、これまでになかったフィールドlocation_type
が含まれている。
気を付けること
-
locations[0]
で値を取り出して、それを拡張する。...
はSpread Operatorといい、既存オブジェクトの要素をすべて含む、という意味だそう。
感想
これらのツールを作ったApolloとprismaは、本当に優れたアーキテクチャの概念・ツール群を提供してくれていると思う。こんなに簡単にできるとはびっくり。
参考