Upgrade to Pro — share decks privately, control downloads, hide ads and more …

本番環境のRailsプロダクトでGraphQL API / GraphQL API on Rails Products in Production

本番環境のRailsプロダクトでGraphQL API / GraphQL API on Rails Products in Production

48f7a397882fae9e111058ab0797e722?s=128

roolrool

July 04, 2019
Tweet

More Decks by roolrool

Other Decks in Programming

Transcript

  1. 本番環境のRailsプロダクトで GraphQL APIを実装した話 2019/7/4 Ryosuke Yamamoto

  2. 2 自己紹介 Ryosuke Yamamoto ( @roolrool ) Webエンジニア 男子シンクロのインストラクター、 Webディレクターを経て2017年1

    月にスペースマーケットにジョイン。 開発比率:バックエンド 5:5 フロントエンド
  3. 3 担当施策で直近の大きなリリース

  4. 4 スペースマーケットEVENTにチケット決済機能追加!

  5. 5 決済リリース前 参照系: GraphQL API / 更新系: REST API 決済リリース後

    すべてGraphQL APIに移行 スペースマーケットEVENTのAPI構成
  6. 6 弊社のGraphQL APIの実装で 工夫しているポイントについて話します! 参考: RailsでGraphQL APIを作る時に悩んだ5つのこと

  7. 7 GraphQL

  8. 8 • クエリ言語とスキーマ言語からなるAPIのための規格 ◦ クエリ ▪ リクエスト用の言語 ▪ 更新系: Mutation

    ▪ 参照系: Query ◦ スキーマ ▪ データ型を定義 • 単一エンドポイント ◦ /graphql GraphQLとは
  9. 9 スキーマ クエリ レスポンス GraphQLの例 クエリをresolverで処理してレスポンスを返す

  10. 10 詳しくはgfxさんの記事をどうぞ

  11. 11 ※前提 APIのフレームワークがRailsなので、 graphql-rubyを使用してGraphQL APIを実装

  12. 12 話すこと • ページネーション • N+1問題 • 画像/ファイルアップロード • アクセス権限管理

  13. 13 ページネーション

  14. 14 • 標準のものはRelayのCursor Connectionというカーソルベースのページ ネーション • graphql-rubyもRelayの仕様をサポート ◦ 導入しやすい GraphQLのページネーション

  15. 15 カーソル・・?? (初めて知った瞬間の脳内 )

  16. 16 • レコードごとにユニークなID(カーソル)を割り当て、そのカーソルを起点として 前後のデータを取得する方式 • レコードの増減によるずれが起きない点がメリット カーソルベースのページネーション

  17. 17 カーソルベースのページネーション node (データそのもの) cursor (edgeごとのユニークなID) page(edges+pageInfo) edge ・・・・

  18. 18 件数 起点

  19. 19 • アプリケーションの仕様上、Offset&Limitページネーションが必要だった • カーソルベースはTwitterタイムラインのような無限スクロールコンテンツには向 いているがページ数を指定したい場合に向かない カーソルベースの問題点

  20. 20 ということで Offset&Limitページネーションを実装

  21. 21 • Railsのページネーションのデファクトと言えばkaminari ◦ ページネーション機能と関連メソッドを提供するgem ▪ 例) object.page(1).per(10).total_pages: 合計ページ数 Offset&Limitページネーションの実装

  22. 22 kaminariを利用して独自のpageInfoを定義

  23. 23 カーソルベース ページネーション比較 Offset&Limit

  24. 24 N+1問題

  25. 25 N+1問題 GraphQLは何も対策しなければサクッとN+1が発生する →resolverを再帰的に実行するのでその度にSQLが  発行されてしまうため

  26. 26 サクッとリクエスト N+1問題

  27. 27 サクッとN+1 N+1問題

  28. 28 N+1?eager loadingだ!(^q^)

  29. 29 • GraphQLに先読み(eager loading)は不向き ◦ 回避自体は可能 ◦ どのテーブルがひかれるかどうかはクエリ次第なので先読みしても使わ れない可能性がある ◦

    associationのfieldを追加する度に先読みを追加することになる eager loadingで回避? SQLを遅延評価したい
  30. 30 • resolverの実行の度にSQLを解決せず、必要なデータを蓄積してから 最後にまとめて解決する • クエリに対して必要なSQLだけを発行するので無駄がない • javascriptならdataloader • Rubyならgraphql-batch

    SQLの遅延評価
  31. 31 • resolverの実行の度にSQLを解決せず、必要なデータを蓄積してから 最後にまとめて解決する • クエリに対して必要なSQLだけを発行するので無駄がない • javascriptならdataloader • Rubyならgraphql-batch

    SQLの遅延評価
  32. 32 graphql-batch

  33. 33 • Shopify製のライブラリ • 内部で非同期処理のgem(lgierth/promise.rb)を使用している • 単体ではActiveRecordに依存していないが、サンプルが用意されているので その通りに実装すればOK ◦ AssociationLoader

    ◦ RecordLoader graphql-batchとは
  34. 34 AssociationLoader

  35. 35 • has_manyな関連レコードを取得する際に使用 • オブジェクトの関連レコードをすべて返す AssociationLoaderとは

  36. 36 confidential AssociationLoader定義

  37. 37 AssociationLoader呼び出し user.events のイメージ

  38. 38 • 下記理由で使用せず ◦ 弊社では独自にページネーションを実装していたため関連をまるまる取 得する形では要件を満たせなかった ◦ resolver内で絞り込みを行った結果を返したかった AssociationLoaderは不採用

  39. 39 RecordLoader

  40. 40 • belongs_to or has_manyな関連レコードを取得する際に使用 • 指定したモデル名&渡したidのレコードを返す RecordLoaderとは

  41. 41 RecordLoader定義

  42. 42 RecordLoader呼び出し

  43. 43 • idを単体か配列で渡せば遅延評価してくれる ◦ 絞り込んだ結果のレコードセットのidを渡せばいいので柔軟 RecordLoaderを採用

  44. 44 • has_many: ◦ RecordLoader#load_many • belongs_to: ◦ RecordLoader#load •

    has_one: × ※関連の外部キーを持たない場合 ◦ 良い方法を調査中 各関連の対応状況
  45. 45 画像/ファイルアップロード

  46. 46 • GraphQLサーバーへのデータの受け渡しはJSONが一般的 • JSONではバイナリデータを扱うことができない ◦ ファイルのアップロードには工夫が必要 GraphQL×ファイルアップロード

  47. 47 • multipart/form-dataで送信 ◦ htmlのform submitやjsのFormDataオブジェクトを使用 • Base64にエンコードしてJSONで送信 アップロード方法の選択 Base64にエンコードする方法を採用

  48. 48 Base64に対応したアップローダー

  49. 49 バックエンドの実装

  50. 50 フロントエンドの実装

  51. 51 • データサイズが大きくなる ◦ エンコード前と比較して33%増加 • オンメモリで扱うには巨大すぎるデータになってしまう可能性 ◦ リクエストボディのサイズ制限は合わせて必要 Base64の問題点

  52. 52 アクセス権限管理 confidential

  53. 53 • fieldやObjectTypeそのものに対してアクセス制限を入れたい • ログインユーザーの個人情報や売上データ等 ◦ 誰でも引けると困るデータ ◦ 権限チェックをresolver毎に書くのはつらい アクセス権限管理の目的

  54. 54 一括で権限を設定できるライブラリを利用

  55. 55 アクセス権限の定義

  56. 56 schema定義時に宣言

  57. 57 所感など confidential

  58. 58 移行してみて • good ◦ ページに対して必要なデータだけを取得するという柔軟なリクエストが出 来るのはやはり良い ◦ 加工したデータを返すfieldなどRESTではレスポンスサイズを気にして持 たせづらかったものも気軽に追加できるようになった

    • bad ◦ パフォーマンス分析のツールが揃っていない ▪ Apollo ServerならApollo Engine ▪ New Relic • クライアント側でquery名をユニークにする必要あり
  59. None