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

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

Fumina Chihama
February 08, 2024
850

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

Fumina Chihama

February 08, 2024
Tweet

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の 密結合を避けた設計を意識したい