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

GraphQL 「良さ」・「難しさ」 再探訪 〜スタディサプリにおける実例〜

Avatar for Fumina Chihama Fumina Chihama
February 08, 2024
1k

GraphQL 「良さ」・「難しさ」 再探訪 〜スタディサプリにおける実例〜

Avatar for Fumina Chihama

Fumina Chihama

February 08, 2024
Tweet

More Decks by Fumina Chihama

Transcript

  1. #Offers_GraphQL実践LT Agenda | 00 01 02 03 04 About Me

    & Us GraphQL 「良さ」 再探訪 GraphQL 「難しさ」 再探訪 「良さ」「難しさ」を踏まえたスタディサプリでの実際 得られたインサイト 2
  2. #Offers_GraphQL実践LT こんにちは! • 内山 高広 / @highwide • スタディサプリでプロダクト基盤のWeb開発を行っています •

    GraphQLを使っているチーム→使っているチーム→使っていないチー ム...と、スタディサプリの複数チームに携わってきました ◦ Ruby on Rails/node.js/Go/Reactなどを書いてきました • 長男(4歳)と次男(0歳)の子育てを楽しんでいます 4
  3. #Offers_GraphQL実践LT sample: 記事一覧画面 9 Awesome Media Article 1 summary Article

    2 summary Article 3 summary GET /articles …みなさんが作ってるシステム、 本当にこんなシンプルなやつですか?
  4. #Offers_GraphQL実践LT たとえば、こういう感じだったりしませんか? 10 Awesome Media Article 1 summary / author

    Articleに似せた記事広告 (システム的には別Entityとして表現されてる) 求人 情報 ユーザー名/前回ログイン日時 記事カテゴリ一覧(ユーザーの好みでパーソナライズされてる) 記事閲覧数 ランキング 注目の Author 天気 予報 写真 いろいろ 動画 いろいろ Article 2 これは、 「GET /articles」 ...で、いいのか?
  5. #Offers_GraphQL実践LT 複数リソースで成り立つ画面/APIとの向き合い方あれこれ 11 • 複数のリソースの中から代表的なリソースに着目する ◦ 「複数リソースを取得する必要はあるが、あくまでこれは"記事一覧"だ」 • 複数のリソースによって構成される1つの(メタ)リソースを見出す ◦

    「これはDashboardというリソースだ」 • リソース指向ではなく、画面指向やコンポーネント指向な命名をする ◦ 「内部的なAPIルーティングも /api/toppage にしよう」 • 単一リソースを返すシンプルなAPIをクライアントが必要に応じて複数呼ぶ ◦ 「/articles と /authors と /videos と...を非同期で呼ぶ」 ◦ ※ この発表では扱いきれないのですが、React Server Componentとの相性◎
  6. #Offers_GraphQL実践LT その他、RESTfulな設計で直面する難しさ 12 • 同一リソースを扱うがコンテキストによって微妙に異なる表示項目 ◦ 導線や利用端末によって記事詳細ページで表示している情報が異なる仕様だが、 すべて「GET /articles/{id}」で取得している ▪

    本来は不要なデータもまとめて取得してしまう "over-fetching" が起こっ ている • リソース指向よりもユースケース指向で表現したくなるような更新系API ◦ 「accountのactivateを行いたいが、それをリソースのPOST/PUTとして表現 するのはやや"座り"が悪い」
  7. #Offers_GraphQL実践LT これらの課題に対しての、GraphQLという解決策 13 クライアント サーバー GraphQL スキーマ Query: 必要なエンティティの必要なフィールドを 1つのリクエストですべてクエリする

    Mutation: 更新処理に必要なパラメータを渡し、必要 な返り値をクエリする (多くの場合) 単一のAPIエンドポイントで QueryやMutationを待ち受ける ex: /api/graphql
  8. #Offers_GraphQL実践LT これらの課題に対しての、GraphQLという解決策 14 クライアント サーバー GraphQL スキーマ Query: 必要なエンティティの必要なフィールドを 1つのリクエストですべてクエリする

    Mutation: 更新処理に必要なパラメータを渡し、必要 な返り値をクエリする (多くの場合) 単一のAPIエンドポイントで QueryやMutationを待ち受ける ex: /api/graphql over-fetchingの心配もなく、 一画面の表示にに大量のリクエ ストを発行する必要もなくなる リソース指向の APIエンドポイント設計は不要に
  9. #Offers_GraphQL実践LT 「何を返す必要があるのか」の知識をクライアントに寄せられる 15 Awesome Media Article 1 summary / author

    Articleに似せた記事広告 (システム的には別Entityとして表現されてる) 求人 情報 ユーザー名/前回ログイン日時 記事カテゴリ一覧(ユーザーの好みでパーソナライズされてる) 記事閲覧数 ランキング 注目の Author 天気 予報 写真 いろいろ 動画 いろいろ Article 2 ※ 仮に画面で表示したい項目が変 わっても、それが既にスキーマで定義 されたものならば、サーバサイドの開 発は不要 query TopPageQuery { article { title summary } adArticles { title summary } job { (以下略)
  10. #Offers_GraphQL実践LT スキーマ駆動開発によるフロー効率の向上 16 クライアント サーバー GraphQL スキーマ 型の提供: スキーマが決まれば、型への変換ができるの で、サーバーの実装を待たず開発着手可能

    resolverの実装: スキーマで定義されたエンティティや フィールドを実際に返せるような実装 ※ もちろん、クライアント-サーバー間のコントラクトとなるスキーマさえあればスキーマ駆 動開発はできるが、GraphQLスキーマのちょうど良い表現力や、クライアントの型に変換 するエコシステムの充実度合いは、開発体験をより良いものにしている (と、思う。最近だとTypeSpecのことはちょっと気になっている)
  11. #Offers_GraphQL実践LT 単一のエンドポイントに様々なユースケースのリクエストを 行うことによる、これまでの慣習の見直し 18 • Observability(観測性) ◦ SLI/SLOをHTTPエンドポイントごとに計測しているシステムは少なくないはず ◦ すべてのQueryやMutationを受け付ける

    /api/graphql では、そのSuccess Rateを見ても、特定ユースケースの兆候はわからない • Authorization(認可) ◦ たとえばRailsのようなMVCフレームワークの場合、ルーティングに対応する個々 のControllerで認可を行うことが多く、そのためのライブラリも充実している ◦ すべてをgraphql_controllerでハンドリングすることになったとき、どのように 認可を行うべきか
  12. #Offers_GraphQL実践LT N+1はRESTでも起こるが、なぜとりわけ 「GraphQLでは起こりやすい」と言われるのか 20 type Article { title: String! }

    スキーマ定義 「スキーマで定義したfiledをどのように 返すか」を実装するresolver Article: { title: () => { // ここでDBから取得したArticle 1レコードが持つ // titleカラムのデータを返す処理 } }
  13. #Offers_GraphQL実践LT N+1はRESTでも起こるが、なぜとりわけ 「GraphQLでは起こりやすい」と言われるのか 21 type Article { title: String! }

    スキーマ定義 「スキーマで定義したfiledをどのように 返すか」を実装するresolver Article: { title: () => { // ここでDBから取得したArticle 1レコードが持つ // titleカラムのデータを返す処理 } } 素朴な実装をしていると、「Articleを複数一気に取得するようなQuery」が投げら れたとき、「Articleの1件取得」を何度も行ってしまう(N+1) 「単一のfieldをどのようにresolveするか」という実装と、「それが一気に複数回呼 ばれることがある」というユースケースの想定に思考のギャップが生まれやすい?
  14. #Offers_GraphQL実践LT 任意のクエリをクライアントが投げられることへの配慮 22 • スキーマ定義において、ネストした構造を作ることがで き、親-子-親という再帰できる構造も作りうる ◦ ex: 記事の著者 /

    著者が書いた記事一覧 • 結果として、「特定の記事の、著者の記事一覧の、それぞ れの著者の、記事一覧の...」というクエリが書けてしまう • クライアントが任意のクエリを投げられることで、ネスト があまりに深いクエリや、膨大なエンティティを取得しよ うとするクエリが、悪意を持つ者から投げられうる type Article { title: String! author: Author! } type Author { articles: [Article!]! }
  15. #Offers_GraphQL実践LT N+1はセオリー通りData Loaderによる対応が多い 26 • DBアクセスやHTTP requestなどのN+1を起こされたくない処理を batch化するData Loaderという仕組みを導入することが多い •

    GraphQLのエコシステムの中に(詳細な仕組みは違えど)だいたいある • 一方で、以下のようにスタンスが二分されるトピックであると最近知った ◦ 「デフォルトではData Loaderを導入しない」派 (Data Loaderを入れることがチューニング) ◦ 「Data Loaderをデフォルトで入れるが、場合によってはオーバー ヘッドを嫌って外す箇所もある」派 (Data Loaderを外すことがチューニング)
  16. #Offers_GraphQL実践LT 実践Observability: /api/graphql/{query名} 29 • APIエンドポイント別にSLI/SLOを計測するという慣習を維持するため にクライアントは /api/graphql/{query名} を叩くという運用を行っ ているチームもいた

    • このとき /api/graphql/ 以下のルーティングはすべて無視されて、実 際にリクエストのハンドリングを行うのは単一のgraphql controller • 用途が限定されている場合においては、これで十分なケースもありそう
  17. #Offers_GraphQL実践LT あらかじめ登録されたオペレーション以外を弾く: Persisted Query 30 https://blog.studysapuri.jp/entry/2023/01/27/100000 • 本番環境ではpersisted queryによってあら かじめ登録されたクエリのみを許容している

    • クライアントコードにおける新たなクエリを persisted query化するコマンドや、漏れを検 知するGitHub Actionを利用 • HTTP GETでpersisted queryに付与され るパラメーターが長過ぎるエラーに対応したこ とも
  18. #Offers_GraphQL実践LT エコシステムへの依存に自覚的になる 36 • HTTPのハンドラをライブラリなしで書けるような言語もある中で GraphQLを選ぶということは(それなりに重い)依存先を1つ増やすこと • 言語によってGraphQLエコシステムの発達度合いは異なるので、選択でき るライブラリとやりたいことのバランスは確認したい ◦

    ex: その言語の型システム上の恩恵は受けられそう?Schema first or DSL first? SchemaのFederationはできる?サポートしているdirectiveは? • 「ビジネスロジック」やそれが守る「データ」に比べると、相対的にGraphQL エコシステムの方が廃れる可能性の方が高いので、ロジックとGraphQLの 密結合を避けた設計を意識したい