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.7k
1
Share
本番環境のRailsプロダクトでGraphQL API / GraphQL API on Rails Products in Production
roolrool
July 04, 2019
More Decks by roolrool
See All by roolrool
検索のMicroservices化 with Apollo Server
roolrool
2
1.7k
Other Decks in Programming
See All in Programming
Coding at the Speed of Thought: The New Era of Symfony Docker
dunglas
0
4.7k
RSAが破られる前に知っておきたい 耐量子計算機暗号(PQC)入門 / Intro to PQC: Preparing for the Post-RSA Era
mackey0225
3
120
AI活用のコスパを最大化する方法
ochtum
0
380
ローカルで稼働するAI エージェントを超えて / beyond-local-ai-agents
gawa
2
260
Reactive ❤️ Loom: A Forbidden Love Story
franz1981
2
230
PHP 7.4でもOpenTelemetryゼロコード計装がしたい! / PHPerKaigi 2026
arthur1
1
540
의존성 주입과 모듈화
fornewid
0
110
夢の無限スパゲッティ製造機 -実装篇- #phpstudy
o0h
PRO
0
200
Codex CLIのSubagentsによる並列API実装 / Parallel API Implementation with Codex CLI Subagents
takatty
2
860
Laravel Nightwatchの裏側 - Laravel公式Observabilityツールを支える設計と実装
avosalmon
1
320
VueエンジニアがReactを触って感じた_設計の違い
koukimiura
0
160
Going Multiplatform with Your Android App (Android Makers 2026)
zsmb
2
350
Featured
See All Featured
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.7k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.3k
Design in an AI World
tapps
0
190
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
1k
Making the Leap to Tech Lead
cromwellryan
135
9.8k
Building the Perfect Custom Keyboard
takai
2
720
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.9k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
260
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
160
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
110
Building AI with AI
inesmontani
PRO
1
870
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