1
0

More than 1 year has passed since last update.

GraphQLに精通している風を装うためのセリフ3選

Last updated at Posted at 2022-09-27

はじめに

GraphQLに関するOSSにコミットする中で、それ以前のGraphQL APIの開発では気づかなかった、GraphQLの通なポイントを3つのセリフにまとめてみました。知ったかする際に役に立つと思うので、ぜひご活用ください!

1. GraphQLって9割がたフィールドだよね

GraphQLにはQuery, Mutation, Subscriptionの3つがある、というのはご存知だと思います。だいたい以下のような役割分担です。

  • Query: データ取得用
  • Mutation: データ変更用
  • Subscription: サーバからのプッシュ用

例えば「ユーザ一覧を取得するためのusersというQuery」を作ったり「ユーザを新規登録するためのcreateUserというMutation」を作ったりするイメージです。

ここで、userscreateUserをQueryやMutationと呼んでも間違いではないのですが、正確には「Queryというルートオブジェクトのフィールドであるusers」や「MutationというルートオブジェクトのフィールドであるcreateUser」だったりします。

つまり「ユーザ一覧を取得するためのusersというQueryを作ろう」は「ユーザ一覧を取得するためのusersフィールドをQueryオブジェクトに追加しよう」と言い換えたほうが、GraphQL的には正確で、ちゃんと理解しているんだぞ感をかもし出すことができます。

もちろん、単にめんどくさがれる可能性が高いです。

2. インプットにUnion型がないのは残念だよね

GraphQLのインプットにはUnion型がありません。補足しておくと、Union型はA | Bのように、AまたはBのどちらかの型を受け取れる型、みたいなやつです。

なお、アウトプットにはUnion型があります。以下のような感じで、直感的にUnionを定義することができます。しかしこれをインプットとして使おうとすると怒られてしまいます。

union Media = Book | Movie

この仕様はリッチな型定義に慣れた開発者に評判が悪かったのか、インプットでもUnion型をサポートしようよというRFCが立ちました。その後紆余曲折あり@oneOfというディレクティブが導入される予定になっています。

これまでのGraphQLでは、型的に堅牢であろうとすると、例えば以下のように、IDでユーザを取得するためuserByIDとメールアドレスで取得するためのuserByEmailを別々に定義することを求められる場合がありました。

type Query {
    userByID(id: ID!): User
    userByEmail(email: String!): User
}

これを@oneOfを使うことで、以下のように1つのuserというQuery(正確にはQueryオブジェクトのフィールド)にまとめることができます。@oneOfによってidまたはemailのどちらか1つのみが指定されることが保証されるため、型的にストイックな方も満足できる仕様になっていると思われます。

type Query {
    user(by: UserByInput!): User
}

input UserByInput @oneOf {
    id: ID
    email: String
}

参考: Coming Soon To GraphQL: The oneof Input Object

3. ResolveInfoを使いこなして初めてGraphQLに価値が生まれるよね

ResolveInfoは(正式にはGraphQLResolveInof)は、多少込み入った処理をする際に必要になってくる奴だと思ってください。

例えば以下のクエリを例にResolveInfoの利用例を考えてみましょう。このクエリは、各ユーザのidname、さらに各ユーザに紐づく投稿(posts)のidtitleを取得するためのクエリです。

query {
  users {
    id
    name
    posts {
      id
      title
    }
  }
}

GraqhQLではクエリで指定された各フィールドについて、それぞれのフィールドに設定されたresolverを順々に呼び出すことで、最終的な戻り値を生成します。上記のクエリの場合、実際の処理の流れは以下のようになります。

  1. ルートオブジェクトであるQueryのusersフィールドのresolverが呼ばれる
  2. (1)のresolverの実行結果がN件(N > 0)のデータを返す場合、各データについてidnamepostsフィールドのresolverがN回呼ばれる
  3. postsフィールドのresolverの実行結果がM件(M > 0)のデータを返す場合、各データについてidtitleフィールドのresolverがM回呼ばれる

各フィールドのresolverのデフォルト(=resolverが未設定の場合)の挙動は、親フィールドのresolverが返した値(オブジェクト)から、自身のフィールド名のプロパティを返す、というものです。例えば(1)で返される値が[{ id: 1, name: "Jon", posts: [] }]だった場合、(2)のidフィールドのデフォルトresolverは1を返し、nameフィールドは"Jon"postsフィールドは[]を返します。

つまり、大本のusersフィールドのresolverが、指定されたフィールドの値をすべて生成して返すことができれば、それ以外のフィールドはデフォルトresolverに任せることができます。今回の例であれば、usersフィールドのresolver内で、上記のGraphQLクエリをもとに、以下のようなSQLなどを生成しDBから値を取得することができれば良さそうです。

SELECT
  User.id,
  User.name,
  Post.id as postId,
  Post.title
FROM
  User INNER JOIN Post ON User.id = Post.user_id

このような、GraphQLクエリからSQLへの変換の仕組みが、例のHasuraPostGraphileで実装されているものです(たぶん)。そして、そのために必要なのがResolveInfoになります。ResolveInfoからクエリで指定されたフィールドを取得し、そこからSQLを生成する、という流れです。

それ以外にも、各フィールドに処理を任せるより、ResolveInfoを用いてルートのフィールドでまとめて処理をしたほうがパフォーマンスが出るなどの理由で、argsのバリデーションや権限制御などの処理で、ResolveInfoは活躍します。

実際にResolveInfoからどんな値が取得できるのか、詳しくはGraphQL.jsの型定義をご参照ください。

まとめ

  • GraphQLって9割がたフィールドだよね
  • インプットにUnion型がないのは残念だよね
  • ResolveInfoを使いこなして初めてGraphQLに価値が生まれるよね

さり気なくこの3フレーズを使うことで、皆さんの単価などが上がることを願っています。
もし興味が湧いた方は、この記事のきっかけとなったOSSとGraphQLに関するチュートリアルも見てみてください。

1
0
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
1
0