カスタムしながら理解するGraphQL Connection
by
yana-gi
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
1 カスタムしながら理解する GraphQL Connection yana-gi Kaigi on Rails 2024 2024.10.25
Slide 2
Slide 2 text
2 ⾃⼰紹介 minne事業部 プロダクト開発チーム 2022年 中途⼊社 yana-gi やなぎ ● 2021年 フィヨルドブートキャンプ卒業 ● 2022年〜 GMOペパボ株式会社 Webアプリケーションエンジニア ● GitHub : yana-gi ● X : @yana_gis ● ビールとお茶とみはしのあんみつが好き
Slide 3
Slide 3 text
3
Slide 4
Slide 4 text
4 ● webの主な技術スタック ○ Ruby on Rails ○ Next.js ○ MySQL ● 段階的に移⾏中 ○ フロント画⾯をRailsからNext.jsへ ○ APIをREST APIからGraphQL APIへ ● GraphQL APIの実装はgraphql-ruby minne
Slide 5
Slide 5 text
5 ● WebはNext.jsに移⾏済み ● GraphQL APIに移⾏済み この画⾯のAPIの実装の話👉 minneの検索画⾯
Slide 6
Slide 6 text
6 質問 🙋
Slide 7
Slide 7 text
7 GraphQLでAPIを 実装したことがある⽅ 🙋
Slide 8
Slide 8 text
8 GraphQLについて 初めて聞いた or 馴染みがない⽅ 🙋
Slide 9
Slide 9 text
9 1. GraphQLとは 1.1. minneでのGraphQL API使⽤例 1.2. minneで新規検索エンジンを導⼊する 1.3. Connection Type 2. Custom Connection 2.1. クエリをしてからデータを返すまで 2.2. Custom Connectionの実装 2.3. 遅延評価 アジェンダ
Slide 10
Slide 10 text
GraphQLとは 10 クライアントが必要なデータだけを指定して取得できる データクエリ⾔語及びランタイム ™
Slide 11
Slide 11 text
No content
Slide 12
Slide 12 text
Products Category Viewer Cart
Slide 13
Slide 13 text
GraphQLとは 13 エンドポイントで取得するデータを指定する ● GET /api/products.json ● GET /api/categories.json ● GET /api/cart.json ● GET /api/viewer.json 1ページを表⽰するのに4回APIを実⾏する必要がある REST APIの場合
Slide 14
Slide 14 text
14 ● 複数のデータ(ノード)を ⼀つのリクエストで取得する GraphQL APIの場合 GraphQLとは query productPage{ products { ... } categories { ... } cart { ... } viewer { ... } }
Slide 15
Slide 15 text
15 不要なデータのattributesまで取得してしまう Request : GET /api/products.json Response :👉 REST APIの場合 GraphQLとは
Slide 16
Slide 16 text
16 必要なデータ(ノード)の フィールドを指定できる GraphQL APIの場合 GraphQLとは query productPage { products { id name price } categories { id slug name } cart { ... } viewer { ... } }
Slide 17
Slide 17 text
17 ● 既存の検索エンジン(Elasticsearch) ● 新しい検索エンジン 新しい検索エンジンの導⼊ 🆕
Slide 18
Slide 18 text
18 ● クライアントが利⽤するminne APIはGraphQL API ○ クエリは既存の検索エンジンと同じように取得できるように ○ 既存のクエリのページネーションは カーソルに加えてオフセットでも取得できる ● 新検索エンジンのAPIはREST API ○ ページネーションはカーソルページネーションのみ 検索エンジン導⼊の要件
Slide 19
Slide 19 text
19 検索エンジン導⼊の要件
Slide 20
Slide 20 text
20 ● minne側で実装するAPIはGraphQL API ○ クエリは既存の検索エンジンと同じように取得できるように ○ 既存のクエリのページネーションは カーソルに加えてオフセットでも取得できる ● 新検索エンジンのAPIはREST API ○ ページネーションはカーソルページネーションのみ 検索エンジン導⼊の要件
Slide 21
Slide 21 text
21 ● オフセットページネーション ● カーソルページネーション ページネーションの種類
Slide 22
Slide 22 text
● データの開始位置を指定する ● offset と limit で取得する ○ offset : 何件⽬から取得するか ○ limit : 何件取得するか SQLの場合 オフセットページネーション 22 SELECT * FROM products LIMIT 10 OFFSET 30;
Slide 23
Slide 23 text
● 特定のデータポイント(カーソル)を基準に次のデー タを取得する ● 時系列などの特定のデータをキーとして検索する ○ cursor : ソート可能なデータポイント(カーソル) ○ limit : 何件取得するか SQLの場合 カーソルページネーション 23 SELECT * FROM products WHERE id > 30 LIMIT 10;
Slide 24
Slide 24 text
改めて検索エンジンの導⼊要件を確認 24 ◯ オフセット ◯ カーソル ◯ オフセット ✗ カーソル
Slide 25
Slide 25 text
GraphQLでページネーションを簡単に実装できる GraphQL Connection 25
Slide 26
Slide 26 text
● Connection: ノード(データ)を リスト形式で取得する ● Edge: ノード間の関係性や付加情報 ● Node: 実際のデータ本体 Connection のノードの取得クエリ 26 query { products(first: 10) { # ProductConnection Type edges { # ProductEdge Type cursor node { # Product Type name id }, …
Slide 27
Slide 27 text
nodesからでも取得できる 27 Connection のノードの取得クエリ query { products(first: 10) { nodes { name id } } }
Slide 28
Slide 28 text
開始位置を指定する 28 Connection のノードの取得クエリ # abc以降のデータを10件取得する team { members(first: 10, after: "abc") { nodes { id name } } }
Slide 29
Slide 29 text
29 Connection Typeの実装 field :items, Types::ItemType.connection_type, null: false def products object.products # ActiveRecord Relationの場合 end fieldのTypeをconnection_typeに指定する サポートされているクラスの場合、要素を全て渡す
Slide 30
Slide 30 text
● 検索エンジンはREST APIで提供されている ○ ページネーションはoffsetページネーションのみ ● 結果を取得したデータを扱うクラスは ActiveRecordではない ○ Connection Typeでサポートされていないクラスになりそう ○ サポートされていない場合は?🤔 改めて今回やりたいこと 30
Slide 31
Slide 31 text
Custom Connection 31
Slide 32
Slide 32 text
Custom Connectionを実装する Custom Connection Typeの実装 32 https://graphql-ruby.org/pagination/using_connections.html#return-collections def products # ActiveRecordのリレーションを取得 relation = object.items # 作成したCustom Connection Typeのインスタンスを返す Connections::ProductsConnection.new(relation) end 参考: https://graphql-ruby.org/pagination/using_connections.html#return-collections
Slide 33
Slide 33 text
GraphQL::Pagination::Connection を継承したConnection Typeを作る 実装する必要があるメソッドが4つある Custom Connectionの実装 33 参考: https://graphql-ruby.org/pagination/custom_connections.html class Connections::ProductsConnection < GraphQL::Pagination::Connection def nodes; ...; end # 返すべきノードの配列を取得 def has_next_page; ...; end # 次のページが存在するかを判定 def has_previous_page; ...; end # 前のページが存在するかを判定 def cursor_for(item); ...; end # 指定されたノードのカーソルを⽣成 end
Slide 34
Slide 34 text
● offset(開始位置)はどこで渡している? ○ 検索エンジンAPIリクエスト時にoffsetを指定できない ⾃分が疑問に思ったこと 🤔 34
Slide 35
Slide 35 text
真相を知るために、クエリを実⾏してから値が返却される までを追う ● シンプルなActive Recordの場合 ● Active RecordかつConnectionの場合 クエリを実⾏してから値が返却されるまで 35
Slide 36
Slide 36 text
クエリを実⾏してから値が返却されるまで 単純にProductクラスを返す場合 36
Slide 37
Slide 37 text
クエリを実⾏してから値が返却されるまで Active RecordでConnectionを返す場合 1 37
Slide 38
Slide 38 text
クエリを実⾏してから値が返却されるまで Active RecordでConnectionを返す場合 2 38
Slide 39
Slide 39 text
クエリを実⾏してから値が返却されるまで Active RecordでConnectionを返す場合 3 39
Slide 40
Slide 40 text
クエリを実⾏してから値が返却されるまで Active RecordでConnectionを返す場合 4 40 offsetやlimitの計算
Slide 41
Slide 41 text
1. 疑問に思ったことの答え ● Connection の offset (開始位置)はどうやって決まる? → Connection Class でcursorから計算している 2. resolverを通った後にConnection Class で offset が決まる わかったこと💡 41
Slide 42
Slide 42 text
● resolverの時点ではoffsetとlimitが決まっていない ● resolverの段階でAPI リクエストをするにもoffsetと limitが決まっていない さらなる疑問🤔 42
Slide 43
Slide 43 text
● Promiseクラスで遅延評価を⾏う ○ ※ 既存の検索エンジンの実装を流⽤ 答え 43
Slide 44
Slide 44 text
遅延評価の流れ1 44
Slide 45
Slide 45 text
45 遅延評価の流れ2
Slide 46
Slide 46 text
46 遅延評価の流れ3
Slide 47
Slide 47 text
クエリをしてから値が返却されるまで1 47
Slide 48
Slide 48 text
クエリをしてから値が返却されるまで2 48
Slide 49
Slide 49 text
クエリをしてから値が返却されるまで3 49
Slide 50
Slide 50 text
クエリをしてから値が返却されるまで4 50
Slide 51
Slide 51 text
1. GraphQLでページングを⾏うにはConnectionを使う 2. 独⾃のモデルでConnectionを使うにはCustom Connectionを定義する 3. Connection Type Classでoffsetが決まる 4. offsetを利⽤するためにresolverではPromiseクラスを利 ⽤して遅延評価を⾏う まとめ & 実装してみて分かったこと 51
Slide 52
Slide 52 text
● 1回のクエリで検索エンジン APIに複数回リクエストがされ ている 1. total_countやhas_next_pageなどを計算するタイミング 2. productsの値を取得するタイミング ● total_countやhas_next_pageのみを取得するエンドポイン トを作ってもらうなどの対応が必要そう 課題 52
Slide 53
Slide 53 text
● チームメンバー ○ kazu(@kazuhitonakayam)さん ○ saki(@Saki-htr)さん ● 資料のレビュー ○ daiki(@doew)さん ○ pyamaさん ● ⼤元のPromiseクラスやCustom Connection Typeの実装 ○ ogidowさん special thanks 53
Slide 54
Slide 54 text
54 Thank you! よいGraphQLライフを!