Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
本番環境のRailsプロダクトでGraphQL API / GraphQL API on Ra...
Search
roolrool
July 04, 2019
Programming
1
1.7k
本番環境のRailsプロダクトでGraphQL API / GraphQL API on Rails Products in Production
roolrool
July 04, 2019
Tweet
Share
More Decks by roolrool
See All by roolrool
検索のMicroservices化 with Apollo Server
roolrool
2
1.7k
Other Decks in Programming
See All in Programming
Swift Concurrency 年表クイズ
omochi
3
230
チーム開発の “地ならし"
konifar
7
4.9k
関数の挙動書き換える
takatofukui
4
660
無秩序からの脱却 / Emergence from chaos
nrslib
1
790
Verilator + Rust + gRPC と Efinix の RISC-V でAIアクセラレータをAIで作ってる話 RTLを語る会(18) 2025/11/08
ryuz88
0
360
AI駆動開発ライフサイクル(AI-DLC)のホワイトペーパーを解説
swxhariu5
0
1k
組織もソフトウェアも難しく考えない、もっとシンプルな考え方で設計する #phpconfuk
o0h
PRO
10
4.4k
AI 時代だからこそ抑えたい「価値のある」PHP ユニットテストを書く技術 #phpconfuk / phpcon-fukuoka-2025
shogogg
1
530
Phronetic Team with AI - Agile Japan 2025 closing
hiranabe
2
620
Claude Code on the Web を超える!? Codex Cloud の実践テク5選
sunagaku
0
550
DartASTとその活用
sotaatos
2
130
OSS開発者の憂鬱
yusukebe
12
4.3k
Featured
See All Featured
Facilitating Awesome Meetings
lara
57
6.6k
Docker and Python
trallard
46
3.7k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
285
14k
Optimizing for Happiness
mojombo
379
70k
Site-Speed That Sticks
csswizardry
13
960
Music & Morning Musume
bryan
46
6.9k
Learning to Love Humans: Emotional Interface Design
aarron
274
41k
GraphQLの誤解/rethinking-graphql
sonatard
73
11k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.5k
It's Worth the Effort
3n
187
28k
Making Projects Easy
brettharned
120
6.5k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Transcript
本番環境のRailsプロダクトで GraphQL APIを実装した話 2019/7/4 Ryosuke Yamamoto
2 自己紹介 Ryosuke Yamamoto ( @roolrool ) Webエンジニア 男子シンクロのインストラクター、 Webディレクターを経て2017年1
月にスペースマーケットにジョイン。 開発比率:バックエンド 5:5 フロントエンド
3 担当施策で直近の大きなリリース
4 スペースマーケットEVENTにチケット決済機能追加!
5 決済リリース前 参照系: GraphQL API / 更新系: REST API 決済リリース後
すべてGraphQL APIに移行 スペースマーケットEVENTのAPI構成
6 弊社のGraphQL APIの実装で 工夫しているポイントについて話します! 参考: RailsでGraphQL APIを作る時に悩んだ5つのこと
7 GraphQL
8 • クエリ言語とスキーマ言語からなるAPIのための規格 ◦ クエリ ▪ リクエスト用の言語 ▪ 更新系: Mutation
▪ 参照系: Query ◦ スキーマ ▪ データ型を定義 • 単一エンドポイント ◦ /graphql GraphQLとは
9 スキーマ クエリ レスポンス GraphQLの例 クエリをresolverで処理してレスポンスを返す
10 詳しくはgfxさんの記事をどうぞ
11 ※前提 APIのフレームワークがRailsなので、 graphql-rubyを使用してGraphQL APIを実装
12 話すこと • ページネーション • N+1問題 • 画像/ファイルアップロード • アクセス権限管理
13 ページネーション
14 • 標準のものはRelayのCursor Connectionというカーソルベースのページ ネーション • graphql-rubyもRelayの仕様をサポート ◦ 導入しやすい GraphQLのページネーション
15 カーソル・・?? (初めて知った瞬間の脳内 )
16 • レコードごとにユニークなID(カーソル)を割り当て、そのカーソルを起点として 前後のデータを取得する方式 • レコードの増減によるずれが起きない点がメリット カーソルベースのページネーション
17 カーソルベースのページネーション node (データそのもの) cursor (edgeごとのユニークなID) page(edges+pageInfo) edge ・・・・
18 件数 起点
19 • アプリケーションの仕様上、Offset&Limitページネーションが必要だった • カーソルベースはTwitterタイムラインのような無限スクロールコンテンツには向 いているがページ数を指定したい場合に向かない カーソルベースの問題点
20 ということで Offset&Limitページネーションを実装
21 • Railsのページネーションのデファクトと言えばkaminari ◦ ページネーション機能と関連メソッドを提供するgem ▪ 例) object.page(1).per(10).total_pages: 合計ページ数 Offset&Limitページネーションの実装
22 kaminariを利用して独自のpageInfoを定義
23 カーソルベース ページネーション比較 Offset&Limit
24 N+1問題
25 N+1問題 GraphQLは何も対策しなければサクッとN+1が発生する →resolverを再帰的に実行するのでその度にSQLが 発行されてしまうため
26 サクッとリクエスト N+1問題
27 サクッとN+1 N+1問題
28 N+1?eager loadingだ!(^q^)
29 • GraphQLに先読み(eager loading)は不向き ◦ 回避自体は可能 ◦ どのテーブルがひかれるかどうかはクエリ次第なので先読みしても使わ れない可能性がある ◦
associationのfieldを追加する度に先読みを追加することになる eager loadingで回避? SQLを遅延評価したい
30 • resolverの実行の度にSQLを解決せず、必要なデータを蓄積してから 最後にまとめて解決する • クエリに対して必要なSQLだけを発行するので無駄がない • javascriptならdataloader • Rubyならgraphql-batch
SQLの遅延評価
31 • resolverの実行の度にSQLを解決せず、必要なデータを蓄積してから 最後にまとめて解決する • クエリに対して必要なSQLだけを発行するので無駄がない • javascriptならdataloader • Rubyならgraphql-batch
SQLの遅延評価
32 graphql-batch
33 • Shopify製のライブラリ • 内部で非同期処理のgem(lgierth/promise.rb)を使用している • 単体ではActiveRecordに依存していないが、サンプルが用意されているので その通りに実装すればOK ◦ AssociationLoader
◦ RecordLoader graphql-batchとは
34 AssociationLoader
35 • has_manyな関連レコードを取得する際に使用 • オブジェクトの関連レコードをすべて返す AssociationLoaderとは
36 confidential AssociationLoader定義
37 AssociationLoader呼び出し user.events のイメージ
38 • 下記理由で使用せず ◦ 弊社では独自にページネーションを実装していたため関連をまるまる取 得する形では要件を満たせなかった ◦ resolver内で絞り込みを行った結果を返したかった AssociationLoaderは不採用
39 RecordLoader
40 • belongs_to or has_manyな関連レコードを取得する際に使用 • 指定したモデル名&渡したidのレコードを返す RecordLoaderとは
41 RecordLoader定義
42 RecordLoader呼び出し
43 • idを単体か配列で渡せば遅延評価してくれる ◦ 絞り込んだ結果のレコードセットのidを渡せばいいので柔軟 RecordLoaderを採用
44 • has_many: ◦ RecordLoader#load_many • belongs_to: ◦ RecordLoader#load •
has_one: × ※関連の外部キーを持たない場合 ◦ 良い方法を調査中 各関連の対応状況
45 画像/ファイルアップロード
46 • GraphQLサーバーへのデータの受け渡しはJSONが一般的 • JSONではバイナリデータを扱うことができない ◦ ファイルのアップロードには工夫が必要 GraphQL×ファイルアップロード
47 • multipart/form-dataで送信 ◦ htmlのform submitやjsのFormDataオブジェクトを使用 • Base64にエンコードしてJSONで送信 アップロード方法の選択 Base64にエンコードする方法を採用
48 Base64に対応したアップローダー
49 バックエンドの実装
50 フロントエンドの実装
51 • データサイズが大きくなる ◦ エンコード前と比較して33%増加 • オンメモリで扱うには巨大すぎるデータになってしまう可能性 ◦ リクエストボディのサイズ制限は合わせて必要 Base64の問題点
52 アクセス権限管理 confidential
53 • fieldやObjectTypeそのものに対してアクセス制限を入れたい • ログインユーザーの個人情報や売上データ等 ◦ 誰でも引けると困るデータ ◦ 権限チェックをresolver毎に書くのはつらい アクセス権限管理の目的
54 一括で権限を設定できるライブラリを利用
55 アクセス権限の定義
56 schema定義時に宣言
57 所感など confidential
58 移行してみて • good ◦ ページに対して必要なデータだけを取得するという柔軟なリクエストが出 来るのはやはり良い ◦ 加工したデータを返すfieldなどRESTではレスポンスサイズを気にして持 たせづらかったものも気軽に追加できるようになった
• bad ◦ パフォーマンス分析のツールが揃っていない ▪ Apollo ServerならApollo Engine ▪ New Relic • クライアント側でquery名をユニークにする必要あり
None