🐈

GraphQL調査 ~実装編~

2022/08/12に公開

前回はgqlgenを利用してGraphQLを最低限動かし、N+1クエリ問題に対処しました。

今回は前回残していた以下三つの課題を実装します。

  1. セキュリティ課題への対処。
  2. ページネーションの実装
  3. React, TSによるクライアントサイドの実装

セキュリティ課題への対処。

スキーマによっては攻撃者が自由に重い処理を走らせることができます。

考えられる主なケースとしては以下です。

  • 多重(無限)ネスト
  • 重い処理を複数回実行

多重(無限)ネスト

{
  user {
    friends {
      friends {
        friends {
          ...
        }
      }
    }
  }
}

重い処理を複数回実行

{
  user {
    friends(first: 100) {
      heavyFieldA
    }
    favorites(first: 100) {
      product {
        heavyFieldB
      }
    }
  }
}

対処としては主に二つあります。

  • クエリの深さを制限する。
  • 複雑度を定義して上限を設ける。

問題との関係性は以下の表に示されます。

対処 \ 問題 多重(無限)ネスト 重い処理を複数回実行
クエリの深さを制限する。 o 防げない
複雑度を定義して上限を設ける o o

実装コストは以下の表の通りだと考えます

コスト 特徴
クエリの深さを制限する。 安い 機械的
複雑度を定義して上限を設ける 中 ~ 高 人間が逐一定義

これらのことを踏まえると重いフィールドを公開していない場合は深さ制限で良いと思います。
今回はクエリの深さ制限を実装しました。
実サービスを運用するとなれば、いずれ複雑度の制限が必要になると考えられます。

https://github.com/nishisuke/example-gqlgen/blob/main/server.go#L51

今回の実装では以下のクエリが深さ5とみなされます。深さ6以上のクエリを弾くようにしました。

query Todos {
  todos(first:1){ 
    edges {
      node {
        user{
          name
        }
      }
    }
  }
}

ページネーションの実装

ベストプラクティスにはカーソルベースのページネーションが紹介されていました。
カーソル形式のページネーションで困ることは思いつかないので、素直にこれを実装します。

定義はここに書いてあります。

定義にはないですが、今回の実装では
ベストプラクティスに乗っていたのでtotalCountフィールドをConnectionに生やしています。

https://github.com/nishisuke/example-gqlgen/blob/main/graph/schema.graphqls#L23-L38

React, TSによるクライアントサイドの実装

graphql.schemaから型が生成されればあとはなんでもいいなと思いながら実装しました。
React, TypescriptでGraphQLを扱う選択肢でメジャーなものは二つです。

今回はApollo Clientを利用します。
Apollo Client単体ではTypescriptの型を生成でいないのでGrapQl Code Generatorも使用します。

Apollo Clientを使う理由

前回GraphQLの主なメリットは

バックエンドにデータの組み立てを移譲できる

と書きました。

これは複数プラットフォームにサービスを展開している場合にわかりやすく恩恵を受けます。

Apollo ClientはSwift, Kotlinもサポートしていそうなので選択しました。

RelayはReact以外のサポートは薄そうに感じました。

クライアントがReactだけの場合むしろRelayを選択肢そうです。
理由はFragmentが簡単に使えたからです。

Apollo ClientでFragmentを使用する方法は今回扱いきれませんでした。(週一ブログ公開という時間制約)

以下Relayの実装
https://github.com/nishisuke/example-react-relay/blob/main/src/Component/User.tsx#L10-L25

Apollo ClientでのReact実装

https://github.com/nishisuke/example-react-apollo-client/blob/main/pages/index.tsx

型生成

クエリを書いて
https://github.com/nishisuke/example-react-apollo-client/blob/main/query/todosQuery.graphql

yarn run graphql-codegen --config codegen.yml

成功したらこう使います
https://github.com/nishisuke/example-react-apollo-client/blob/main/pages/index.tsx

まとめ

今回は以下を実装しました。

  1. セキュリティ課題への対処。
  2. ページネーションの実装
  3. React, TSによるクライアントサイドの実装

前回バックエンド側の実装コストが安くないと言いましたが、その分フロントがシンプルになることを実感しました。
フロントエンドのコードは量が多くなりがちでレビューが大変だとちょうど最近感じていました。
iOSやAndroidでのGraphqlのライブラリが整っているかは未検証なので、フロントがウェブのみの新規サービスは積極的にGraphQLを使用したいと思いました。

Discussion