GraphQL調査
目的
本番環境でGraphQLを使うコスト感を知る。
どんなサービスでなら使用しやすいかを知る。
環境
- macOSMonterey 12.2.1
- x86_64
- go1.18
- gqlgen
- dataloader
- gorm
今回はgqlgenでgraphql schemaからgoのコードを生成します。
色々調べたけどライブラリはgqlgen一択だと感じました。
まずは動かす
準備
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を実装
長くなるので使用したコードを置いておきます
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