GraphQLスキーマをカスタマイズする
先日、markdownのimgをgraphQLで管理しようとしたところ、型の類推エラーによってgraphQLのスキーマをいじらなければならなくなりました。なのでこのガイドを学習します!
ハイパー意訳がほぼほぼですのでご了承ください🙇♂️
graphQLのスキーマ言語をおおよそ知る必要がある
その前に、そもそも、graphQLに関する記述法を知らなかったので、そこをざっくり学習します。
https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000
私は上記で学習させていただきました!
特に必要となる知識は、スキーマ言語(Web APIの仕様を定義する方)の
fieldの定義方法と,Interfaceについてです。
type Query {
currentUser: User!
}
interface Node {
id: ID!
}
Interfaceは必要となるfieldのセットを束ねた抽象型です。
公式ガイド
では公式ガイドを見てみます。
このガイドは
-
プラグインの作成者
-
自動型類推によって作成されたスキーマを修正したい方(私)
-
ビルドの最適化を測る方
-
スキーマに興味のある方
を対象としているようです。こちらかなり長いのですが、ここでは前編部分のみを扱います。
気になる方は続きを読むと良さそうです!
graphQLの強みは何と言っても多くの情報源を一括で扱えること!
そのためにはGraphQLスキーマを生成しなければなりません。
例 マークダウンとJSON
Markdownファイルからブログ記事を生成する。記事内容に加えて筆者情報をJSON形式で提供する。
時々ある寄稿者の情報は別のJSONとして保存する。
---
title: Sample Post
publishedAt: 2019-04-01
author: jane@example.com
tags:
- wow
---
# Heading
Text
[
{
"name": "Doe",
"firstName": "Jane",
"email": "jane@example.com",
"joinedAt": "2018-01-01"
}
]
[
{
"name": "Doe",
"firstName": "Zoe",
"email": "zoe@example.com",
"receivedSwag": true
}
]
これらをGraphQLでクエリするためにはgatsby-source-filesystem,gatsby-transformer-remark,gatsby-transformer-json
を使用しますが、これらのプラグイン内部で何が行われているかというと、
markdownファイルをユニークなidとMarkdownRemarkタイプを持つnodeオブジェクトに変換しています。
同様に著者データはAuthorJsonタイプのnodeオブジェクトに変換され、
寄稿者データはContributorJson型のノードオブジェクトに変換されています。

Node interface
Source pluginやtransformer pluginによって作られるGatsbyのGraphQLのスキーマです。
Id, parent,children,またinternal型などといったfieldがセットされています。
interface Node {
id: ID!
parent: Node!
children: [Node!]!
internal: Internal!
}
type Internal {
type: String!
}
** TODO: internalって何でしょう..イマイチ調べてもわからずでした。
例 autor.json
例)Gatsby-transformer-jsonで作成するautor.json用のnode型は、以下のようになっています。
type AuthorJson implements Node {
id: ID!
parent: Node!
children: [Node!]!
internal: Internal!
name: String
firstName: String
email: String
joinedAt: Date
}
実際にgatsbyが作成したスキーマを確認するには、GraphQL Playground
がオススメです。
プロジェクト上で
$ GATSBY_GRAPHQL_IDE=playground gatsby develop
そしてhttp://localhost:8000/___graphql
を開いて右画面側にある Schemeタブを開きます。
思った以上にぎっしりしてます。
自動型推論
先ほどのautor.jsonを見てみましょう。
[
{
"name": "Doe",
"firstName": "Jane",
"email": "jane@example.com",
"joinedAt": "2018-01-01"
}
]
どこにも型定義などされていないですが、GraphQLではうまいこと動かせています。
これは型の類推を行なってくれているからですね。
1つ1つのfield内容を確認し、型をチェックすることで実現しています。
しかしこの型類推には2つの問題点があります。
-
時間がかかり、負担がかかる
-
型とデータが異なっていた場合、型の類推が失敗する
2つ目についてですが、例をあげます。
.md+ { + "name": "Doe", + "firstName": "John", + "email": "john@example.com", + "joinedAt": "201-04-02" + } ]
joinedAt部分がスペルミスによって、Dateと解釈できなくなっています。
これらを解決するためには、型を明示的に示すことです。
型定義
createType action
を使って型を明示的に定義します。gatsby-node.jsexports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions const typeDefs = ` type AuthorJson implements Node { joinedAt: Date } ` createTypes(typeDefs) }
全ての型を定義する必要がない点に注意してください。(name,firstName等)
そもそも型推論をやらない!というストイックな方もいるでしょう。
これを行うことでパフォーマンスの向上が見込めます。先ほども開設したように、型の類推は負担が大きいため、大規模なプロジェクトほどパフォーマンス低下が著しくなります。
@dontInfer
ディレクティブを用いて型推論をオプトアウトできます。
ディレクティブってなんだ?という方は先ほどのGraphQLスキーマについての解説をどうぞ!https://employment.en-japan.com/engineerhub/entry/2018/12/26/103000)
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type AuthorJson implements Node @dontInfer {
name: String!
firstName: String!
email: String!
joinedAt: Date
}
`
createTypes(typeDefs)
}
これがストイックなバージョンになります。
Node interfaceが提供するフィールドについては、Gatsby側で追加してくれるので明示しなくて大丈夫です。(id,parentなど)
ネストしている型
今まではString型やDate型などのスカラー型のみを扱ってきました。GraphQLでは他にも
-
ID型
-
Int型
-
Float型
-
Boolean型
-
JSON型
なども扱うことが可能です。また、複雑なオブジェクト値だって扱えます。
markdown-remarkを例にとってみます。
以下の記述によって
frontmatter.tags
は必ず文字の配列となります。gatsby-node.jsexports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions const typeDefs = ` type MarkdownRemark implements Node { frontmatter: Frontmatter } type Frontmatter { tags: [String!]! } ` createTypes(typeDefs) }
こちらの記述のように、直接Frontmatter型を指定すると失敗してしまいます。
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
# これは失敗!!!
type Frontmatter {
tags: [String]!
}
`
createTypes(typeDefs)
}
なぜなら、Frontmatter型がソースプラグインやトランフォーマープラグインによって作成されないため、Nodeインターフェースが実装されくなってしまうからです。
そのためにこのような処理となります。