📘

GraphQL調査

2022/08/05に公開

目的

本番環境でGraphQLを使うコスト感を知る。
どんなサービスでなら使用しやすいかを知る。

環境

今回はgqlgenでgraphql schemaからgoのコードを生成します。

色々調べたけどライブラリはgqlgen一択だと感じました。

まずは動かす

準備

cf. https://gqlgen.com/

mkdir example
cd example
git init
go mod init example
printf '// +build tools\npackage tools\nimport _ "github.com/99designs/gqlgen"' | gofmt > tools.go
go mod tidy
go run github.com/99designs/gqlgen init

Schema編集

Usersをクエリできるようにします。
UserはfriendsとしてUserの配列を持つものとします。

vim graph/schema.graphqls

 type User {
   id: ID!
   name: String!
+  friends: [User!]!
 }

 type Query {
   todos: [Todo!]!
+  users: [User!]!
 }

vim gqlgen.yml 最終行に追加

       - github.com/99designs/gqlgen/graphql.Int32
+  User:
+    fields:
+      friends:
+        resolver: true

vim graph/resolver.go

 package graph

+//go:generate go run github.com/99designs/gqlgen generate
+
 // This file will not be regenerated automatically.

コード生成

go mod tidy
go generate ./...

Resolverを実装

長くなるので使用したコードを置いておきます
https://github.com/nishisuke/example-gqlgen

Playground起動

go run server.go
open http://localhost:8080


上記の写真のように、左側に以下を入力しCtrl+EnterでQuery実行できます

{
  users() {
    id, name, friends {
      id, name, friends {
        id, name
      }
    } 
  }
}

動かした感想

Restに比べれば実装コストが高いと感じました。
後述するN+1クエリを抑制するためにdataloaderを使用しました。
dataloaderなしで実運用できるとはとても思えないです。
dataloaderの実装込みで、コストが高いと感じました。
ページやモデルが少ないサービスならRestだなと思いました。

GraphQLに期待しているメリット

GraphQLを導入することで得られるメリットは主に次の点だと思います。

RestとGraphQLで比較してみましょう。

Rest APIだと

GET /users/{id}
GET /users/{id}/orders
GET /products

3つのレスポンスをクライアント側が組み立てる。

GraphQLだと

query {
  users(id: "me") {
    id
    name
    orders {
      id
      product {
        id
        name
        price
      }
    }
  }
}

レスポンス構造はクライアントが指定する。
バックエンドが指定された構造を組み立てる。

この組み立てがバックエンドに移譲されています。

GET /me-orders-product(名前雑ですまん)

このAPI一つを考えると、ライアントサイドから見える体験は同じです。
しかしクライアントの変更には常にバックエンドの変更も伴うことになります。
さらにクライアント毎、ページ毎に専用のAPIが必要になります。

よってデータの組み立てをバックエンドに寄せたい場合GraphQLは有用と考えられます。

これはバックエンドの人材が手厚い場合や、
Web, iOS, Androidなど複数プラットフォームにサービスを展開している場合などでしょうか?

懸念事項

懸念事項としては以下がまず考えられます。

  • APIのバージョニング
  • 良いSchemaを設計できるか
  • クエリ数の増加
  • セキュリィティ

APIのバージョニング

Best Practiceを見るとバージョニングを避けろと書いてありました。
つまりは破壊的変更を避けろということです。

Schemaにフィールドをを追加していく限りはある程度破壊的変更を避けることができます。
ただ、公開していたMutationを非公開にしたい場合どうすればいいのか調査する必要があります。
継続的なサービス開発においては使用されていない機能をコードから削除することは最上位に重要なことだと考えます。

Schemaに関してもフィールドの追加は簡単ですが、削除は同様な理由で避けたいです。
これは次の 良いSchemaを設計できるか にも繋がってきます。

iOSなどのネイティブアプリを提供している場合には、APIのバージョニングもしくはクライアントの強制アップデートが必須です。
クライアントの強制アップデートは考慮すべきことが蓄積されていくと思っているので個人的には避けたいです。

良いSchemaを設計できるか

こればっかりはやってみないとわからないですが以下が要素としてあるかなと思います。
サービスによるので、あまり汎用的なことは書けないと思っています。

  • ドメインが洗練されているか (単純なドメインか)
  • メリットの点で書いた、データの組み立てをサーバーサイドに徹底的に寄せ切れるか
  • Mutationで更新されるフィールドをレスポンスとして返せるか
  • もしくはVersioningをすると決めてしまうか?(その場合gqlgenでどうやるか調査)

クエリ数の増加

単純に実装するとN+1が発生します。
幸いなことにdataloderなどの解決策が普及しています。

セキュリィティ

攻撃者は無限ループや、重い処理を狙って実行させることができます。

query {
  user(id: "id") {
    friends {
      friends {
        friends {
          ...
        }
      }
    }
  }
}

こういうケースへの対処も必要になります。

まとめ

バックエンドの実装コストは安くないと感じました。

コストがかかる以上、

というメリットを存分に享受できるかが判断基準になりそうです。
以下の項目にはいと答える数が多いほどGraphQLを採用しそうと感じました。

  • 大きいサービスか
  • 複数プラットフォームでサービスを提供しているか
  • 破壊的変更なしでSchemaを洗練していける自信があるか
  • バックエンド人材が豊富か

注:私は本番環境でGraphQLで実装したことがないです。全ては考察です。

次回

次回は以下にとりかかります。

  • React, TSによるクライアントサイドの実装
  • セキュリティ課題への対処。
  • ページネーションの実装
  • キャッシュについて考える

調べたメモ

  • IDはグローバルでユニークにする
  • クエリはdepthを制限する
  • クエリのcomplexityを計算して攻撃を弾く

Discussion