LoginSignup
13
5

More than 5 years have passed since last update.

Schema Stitching: 複数GraphQL APIをくっつけて新しいAPIを作ろう

Last updated at Posted at 2018-08-11

何のため?

前提

  • 目的は、一つのAPIで複数データベースの検索や操作
  • 複数のデータベースがすでにあり、それぞれにGraphQL APIがある(一部はREST APIでもよい)。これを以降、生APIと呼ぶ。

やりたいこと

  • 生APIはそのままでは使いづらいため、使いやすい新しいAPIを作りたい
  • ただし、フルスクラッチで作りたくはない。スキーマなどはコピペで楽したい

Schema Stitching (スキーマスティッチング)とは

Schema stitching (スキーマスティッチング)とは、「スキーマのつぎはぎ」

以下のようなメリットがある。

  • 既存の複数のGraphQL APIからスキーマをコピペするように取り込むことができる (makeRemoteExecutableSchema)
  • 複数スキーマをつぎはぎし、新しいAPIスキーマを自動作成する (mergeSchema)

さっそくやってみる

既存のGraphQL APIとして下記がある。

  • users:ユーザ情報
  • locations:位置情報
  • usersaddressフィールドがlocationsaddressフィールドとJOIN可能。
users locations
image.png image.png

スキーマスティッチングを下記のように実施する。

stitching2.png

  • usersのスキーマの中に新フィールドlocationsを用意。
  • 新フィールドlocationsは、usersaddressと同じaddressを持ったlocations の配列。

Schema stitchingのソースコード

  • users, locationsは個別にGraphQLServerを準備

スティッチング用BFFサーバでは、以下を実施する。

  • 1. 上記既存APIをリモートスキーマとして読み込む
    • リモートスキーマ読み込みには、graphql-toolsmakeRemoteExecutableSchema, introspectSchemaを使う
  • 2. 既存スキーマにないフィールドusers.locationsを追加するスキーマ差分linkSchemaDefを作成
  • 3. スキーマスティッチング(スキーマのマージ)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というフィールドが追加されており、locationsAPIの結果がjoinされていることがわかる。

image.png

気を付けるところ

  • addressカラムでjoinの実装はargslocationsクエリの引数を入れることで実現する。
    • 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

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5