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

# 何のため？

## 前提

- 目的は、**一つの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|
|---|---|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/7032a3bf-530c-83d1-f3c4-a874fe7ffbe4.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/2afcdb0e-5bdc-f60c-13ea-4b072c6f400b.png)|

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

![stitching2.png](https://qiita-image-store.s3.amazonaws.com/0/96277/d305afe2-7d49-e307-5b79-a4d05eb01ec3.png)

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

## Schema stitchingのソースコード

- `users`, `locations`は個別にGraphQLServerを準備

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

- 1. 上記既存APIを**リモートスキーマとして読み込む**
  - リモートスキーマ読み込みには、`graphql-tools`の`makeRemoteExecutableSchema`, `introspectSchema`を使う
- 2. 既存スキーマにないフィールド`users.locations`を追加する**スキーマ差分`linkSchemaDef`を作成**
- 3. **スキーマスティッチング（スキーマのマージ）`mergeSchemas`を活用**
  - **既存スキーマ＋スキーマ差分を代入**
  - **追加フィールドの内部実装(resolve)を`delegateToSchema`を使い記述**

```typescript
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されていることがわかる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/6caf6c11-d982-c239-2faf-d9301be8162a.png)

## 気を付けるところ

- `address`カラムでjoinの実装は`args`に`locations`クエリの引数を入れることで実現する。
 - `args: {where: {address: parent.address}}`とする
 - これで`locations( {where: {address: <usersのaddress> }} )`というクエリが発行される

# ちょっとスキーマをアレンジする、リクエストをバッチ化する

[次回](https://qiita.com/Hiroyuki_OSAKI/items/6fc76924c6ca175fcdb4)につづく。。。

## 実際のソースコード 

https://github.com/onelittlenightmusic/schema-stitching-example

# 参考

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