Schema Stitching: graphql-bindingで簡単にちょっとスキーマをカスタマイズする

# 何のため？

前回[Schema Stitching: 複数GraphQL APIをくっつけて新しいAPIを作ろう](https://qiita.com/Hiroyuki_OSAKI/items/6cdf878e134ef879eb81)にて、スキーマスティッチングの基本・最低限の実装をしたわけですが、**最低限実装では自由度が低くてなかなか使い物になりません**。

自由度が低いところ

- 型の変更が難しい
  - 既存APIの出力が配列型だとスティッチング結果もそのまま配列型になってしまう
- スキーマの変更が難しい
  - `delegateToSchema`を単純に使うだけでは既存APIと同じスキーマになってしまう

# graphql-bindingでちょっとカスタマイズしてみる

非常に簡単にできます。

### 実装方法概要

- 定義の変更は**スキーマで`extend`**を使う
- 出力処理の変更は**BindingかSchemaTransformを使う**（今回は**Binding**を活用）
  - `location`APIのスキーマをBindingに入力して**bindingオブジェクトを作成**
  - bindingオブジェクトの**`query`を呼び出し、取得`locations`オブジェクトを直接操作**

### 実装

ソースコードは、[前回](https://qiita.com/Hiroyuki_OSAKI/items/6cdf878e134ef879eb81)実装したものに一部追加する。

- **型を変える**
  - 【変更前】既存API `locations`の型が`[Location]`という配列 
    →【変更後】新APIではその要素1つを取り出し、型を`Location`とする
- **スキーマを変える**
  - `Location`スキーマに新フィールド`location_type`を追加する

`users`の内部を以下のように変更する。

- 旧フィールド`locations`をスキーマ変更した新フィールド`location`を作る
- `location`は、`locations`の配列から0番目の要素を取り出して表示する
- さらに既存`locations`APIにはない新フィールド`location_type`を追加し、値を`"LARGE_CITY"`を入力する

```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() {
	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`が含まれている。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96277/f3b720eb-794e-5d0d-4841-5019c3f318dd.png)

### 気を付けること

- `locations[0]`で値を取り出して、それを拡張する。`...`は**Spread Operator**といい、既存オブジェクトの要素をすべて含む、という意味だそう。

### 感想

これらのツールを作ったApolloとprismaは、本当に優れたアーキテクチャの概念・ツール群を提供してくれていると思う。こんなに簡単にできるとはびっくり。

### 参考

https://www.prisma.io/blog/graphql-binding-2-0-improved-api-schema-transforms-automatic-codegen-5934cd039db1/