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

GraphQLの魅力を引き出すAndroidクライアント実装

Kurumi Morimoto
September 11, 2024

 GraphQLの魅力を引き出すAndroidクライアント実装

Kurumi Morimoto

September 11, 2024
Tweet

More Decks by Kurumi Morimoto

Other Decks in Programming

Transcript

  1. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 スタディサプリ小学講座・中学講座 • 小学1,2年生 および 中学1~3年生 向けの月額制のオンライン学習サービス •

    2022年2月に中学講座アプリをリニューアル ◦ 2023年9月に同アプリに組み込む形で小学講座をリニューアル • アプリでは学習に辿り着くまでの導線を実装 ◦ 学習画面は WebView で表示している 4
  2. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 スタディサプリ小学講座・中学講座での GraphQL の活用 • スタディサプリ小学・中学講座では GraphQL を全面採用

    ◦ 一部他サービスと共有している箇所(認証など)は, REST API を利用 • クライアントは API Gateway にアクセス ◦ API Gateway は GraphQL Schema Stitching で複数のスキーマをマージ • API Gateway から下層のマイクロサービスにアクセスが振り分けられる ◦ ユーザーごとの学習記録や, 学習コンテンツの情報を取得して, クライアントに返却 6
  3. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 GraphQL の採用理由 • クライアントが欲しい情報を柔軟に取得できる ◦ オーバーフェッチ /

    アンダーフェッチの心配がない ◦ プラットフォームごとの UI の要求が異なる場合にこの特徴が活きる • クライアントライブラリの型生成が強力 ◦ 定義した GraphQL スキーマを元に, クライアントライブラリで型が生成される ◦ API スキーマと実装の乖離が起こりにくい ◦ => スキーマ駆動開発を加速させる 8
  4. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 本セッションの目的 GraphQL の導入を検討中の方, 既に GraphQL を導入している方に スタディサプリ小学講座・中学講座で

    GraphQL を全面採用し, 2年ほど運用 した中で蓄積された ADR (Architecture Decision Records) を紹介し より効果的に活用するためのノウハウを共有する 9
  5. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ADR | 01 02 03 04 原則としてドメインロジックはサーバーサイドで実装し,

    GraphQL Schema 定義に含める Apollo Client の Wrapper クラスを定義し, 各画面から 呼ぶ GraphQL Query / Mutation を一任する GraphQL Errors と HTTP Status Code のマッピング Fragment Colocation の指針 10
  6. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ドメインロジックは GraphQL Schema 定義に含める • 教育ドメインでは, クライアント間での挙動の差が大きな問題になり得る

    ◦ リアルタイム性や極端に高いパフォーマンス要求はほとんどない • サーバーサイドに実装を寄せることで問題を起こりにくくする • 単にデータソースを返す API にせず, 適切なドメインモデルに落とし込んだ GraphQL Schema を設計する • 原則通りに実装した場合, クライアント側ではドメインロジックは不要に 12
  7. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 例) 「週ごと」の学習記録を取得する API クライアント側で日付の範囲を指定して, 学習記録を取得 するような API

    も設計可能だが, 「集計は月曜開始とする」という仕様をドメインロジック とみなし, サーバーサイド側で計算し, GraphQL Schema に含めた 13
  8. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 (余談) 誰が GraphQL Schema を定義するのか • スキーマは誰が書いても良い

    ◦ サーバーサイド専任のタスクではない • クライアント / サーバーサイド のメンバーでレビューし合って定義 ◦ よりクライアント主体のデータフェッチを実現でき, GraphQL の特性を活かしやすい 14
  9. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 クライアントでは, 原則ドメインロジックの計算をしない • 推奨アーキテクチャでは, UI レイヤとデータレイヤの2つが存在 ◦

    UI レイヤ : 画面にアプリデータを表示 ◦ データレイヤ : ビジネスロジックを含み, アプリデータを公開 https://developer.android.com/topic/architecture?hl=ja#modern-app-architecture 17
  10. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 • 推奨アーキテクチャでは, UI レイヤとデータレイヤの2つが存在 ◦ UI レイヤ

    : 画面にアプリデータを表示 ◦ データレイヤ : ビジネス ロジックを含み, アプリデータを公開 GraphQL Schema がこの役割を担うため, クライアントは実装不要 18 クライアントでは, 原則ドメインロジックの計算をしない
  11. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 クライアントは UI レイヤを実装する • UI レイヤは UI

    要素と状態ホルダで構成 ◦ UI 要素 : Jetpack Compose ◦ 状態ホルダ : AAC の ViewModel (ViewModel の廃止検討については付録で紹介) https://developer.android.com/topic/architecture?hl=ja#ui-layer 19
  12. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 クライアントは UI レイヤを実装する 無理に推奨アーキテクチャに当てはめない • Apollo Client

    をラップした, 実態にそぐわない Repository や UseCase を 定義しない • GraphQL で完結せず, ローカル / リモート Data Source にアクセスする 場合に, 適切に Repository や UseCase を用いる ◦ ローカル Data Source : Room, Preference DataStore ◦ リモート Data Source : REST API with Retrofit 20
  13. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ここまでのまとめ • 原則としてドメインロジックはサーバーサイドで実装し, GraphQL Schema 定義に含める •

    GraphQL で完結する画面については, 推奨アーキテクチャに無理に 当てはめずに, UI レイヤのみを実装する • ローカル / リモート DataSource にアクセスする箇所は, 推奨アー キテクチャを参考に, 適切にドメイン / データレイヤを実装する 21
  14. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Apollo Client の Wrapper クラスを定義する • Apollo

    Client をラップした ApolloWrapper クラスを各画面に定義し, 各 画面から呼ぶ GraphQL Query / Mutation を一任する • ApolloWrapper は Flow<T> で 値を返却し, エラーの場合は例外を投げる • ViewModel は 直接 Apollo Client を叩かず, ApolloWrapper を呼び出す • ViewModel と同じ階層に配置し, UI レイヤであることを強調する • ApolloWrapper は Apollo Client 以外を引数にとらない 32
  15. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Apollo のレスポンスをどうやって Flow<T> に変換するか Apollo の toFlow()

    関数 を呼び出す https://www.apollographql.com/docs/kotlin/kdoc/older/3.8.2/apollo-runtime/com.apollographql.apollo3/-apollo-call/to-flow.html 36
  16. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ApolloResponse には, data と errors が格納されている errors

    が存在する場合は例外を投げつつ, Flow で data を返却する (後述) https://www.apollographql.com/docs/kotlin/kdoc/older/3.8.2/apollo-api/com.apollographql.apollo3.api/-apollo-response/index.html 37 Apollo のレスポンスをどうやって Flow<T> に変換するか
  17. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Apollo Client の Wrapper クラスを定義する ApolloWrapper をクラスを定義している理由は2つ

    • 画面の描画に複数の Query を叩く必要がある場合に, ApolloWrapper が 値のマージ処理を担うことで, ViewModel の処理をシンプルに保つ ◦ 本来はマージが必要になる Schema にすべきではないが, サーバーサイドの都合でやむ を得ない場合がある. Query の結果をそのまま画面に流すのが理想. • ViewModel のテストの容易性が上がる ◦ Apollo Client よりも ApolloWrapper の方が mock しやすい 38
  18. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ViewModel でレスポンスを束ね, 画面の状態を計算する • ViewModel は, ApolloWrapper

    / Repository / UseCase から 受け取った Flow を束ね, Loading / Content / Error の状態を計算する ◦ ApolloWrapper : GraphQL with Apollo ◦ Repository : Room, Preference DataStore, REST API with Retrofit ◦ UseCase : 複数画面から呼び出されるドメインロジック (ログイン / ログアウト処理など) • 状態は LCE の Sealed Class で表現し, StateFlow で流す 44
  19. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ここまでのまとめ • 画面ごとに ApolloWrapper クラスを定義し, GraphQL の処理を一任する

    • ApolloWrapper は, ApolloResponse の data を Flow で返却し, errors が 存在する場合は例外を投げる • ViewModel では, ApolloWrapper / UseCase / Repository の結果を束ね, LCE (Loading / Content / Error) の状態を計算する 50
  20. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 • 公式の GraphQL Over HTTP では, Errors

    の有無と HTTP Status に関係を 持たせず, 常に 200 を返すように推奨されている ◦ 部分的なエラーの場合に, 処理を継続できる可能性がある ◦ クライアント側が HTTP レイヤを気にする必要がないように https://graphql.github.io/graphql-over-http/draft/#sec-application-json 52 GraphQL Errors と HTTP Status Code のマッピング
  21. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 GraphQL Errors と HTTP Status Code のマッピング

    • しかし, 我々はクライアント起因のエラーの場合は, Status Code 400, サーバーサイド起因の場合は 500 を返し, GraphQL Errors にエラーを 含めるという運用を選択している ◦ サービスの構成上, 部分エラーの場合に処理を継続することが困難 ◦ 一部 REST API を呼び出している箇所があり, クライアントが HTTP レイヤを気にする 必要はある ◦ 監視と相性がいい 53
  22. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 (余談) API Gateway の Http Success Rate

    7 / 30 / 90 日間で, 全リクエストのうちの何%が 200 リクエストで返って きているかを, SLO として監視している 54
  23. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 OkHttpClient の Authenticator を用いると, 401 エラーが返却された場合に, 任意の処理を実行できる.

    認証情報をヘッダに付与したリクエストを返却すると自動でリトライが実行 され, null を返却するとリトライをスキップする 認証エラー (401) の場合 (リクエスト後) https://square.github.io/okhttp/recipes/#handling-authentication-kt-java 59
  24. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ここまでのまとめ • クライアント起因のエラーの場合は, HTTP Status Code 400,

    サーバー サイド起因の場合は 500 を返却し, GraphQL Errors にエラーを含める • クライアント起因のエラーは, リクエスト前後で適切にハンドリングする • サーバーサイド起因のエラーの場合は, リトライ処理をしつつ, エラーの 中身は確認せずに, 例外を投げる 62
  25. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 GraphQL Fragment Colocation GraphQL Fragment と UI

    コンポーネントを「一緒に配置する」 (同一ファイル に定義する) ことを, Fragment Colocationと呼ぶ 65
  26. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Fragment Colocation のメリット • UI コンポーネントが必要とするデータがわかりやすい •

    あるコンポーネントが必要とするデータが増減した時に, 修正箇所がその コンポーネント内に閉じるため, メンテナンス性が向上する • フィールドの増減に気づきやすいので, オーバーフェッチを防げる 66
  27. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Apollo Kotlin Plugin Apollo Kotlin を利用している以上, GraphQL

    Fragment の定義と UI コンポー ネントを同一ファイルに記載することはできない Apollo Kotlin plugin を使うことで, 定義元の GraphQL ファイルに簡単にジャ ンプできるので, 同一ファイルに記載することにこだわる必要はない https://www.apollographql.com/blog/announcing-the-apollo-kotlin-plugin-for-android-studio-and-intellij-idea 67
  28. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Fragment Colocation の指針 ① • Composable 関数が

    GraphQL Query / Mutation の フィールドに依存して いる場合は, Preview の単位にあわせて Fragment を切ること ◦ Preview をする場合は, フィールドが1つの場合でも Fragment として切り出す ◦ Preview よりも細かい単位で Fragment を切り出しても良いが, Fragment を切り出しすぎ ると, 階層が深くなり逆にコードの見通しが悪くなる場合があるので注意する • Composable 関数 と Fragment の対応関係を明確にするため, Fragment の命名は, Composable 関数名 + Fragment とする 69
  29. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Fragment Colocation の指針 ② • GraphQL Query

    / Mutation の上の構造が同じであっても, UI コンポー ネントが異なる場合は, 別々の Fragment を定義する • UI コンポーネントと関係のない Fragment を共有しない 71
  30. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ここまでのまとめ • Fragment Colocation を実践すると, コードのメンテナンス性や パフォーマンスの向上が期待できる

    • Apollo Kotlin Plugin を使うと, 定義元の GraphQL ファイルに簡単に ジャンプできる • Composable 関数の Preview の単位で Fragment Colocation をする • UI コンポーネントと関係のない単位で Fragment を切るのは, オーバー フェッチの原因になるので避ける 75
  31. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 総括 GraphQL の魅力を引き出すには.... • ドメインモデルを反映させた GraphQL Schema

    を設計し • 適切に UI レイヤから Apollo Client を呼び出し • プロダクトの特性を考慮してエラーハンドリングを設計し • Composable 関数と GraphQL Fragment を対応させる ことが大切です! 76
  32. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 Kurumi Morimoto (morux2) StudySapuri at Recruit Co.,

    Ltd. morux2 _morux2 付録には以下2点を掲載しています • Persisted Query に変わるクエリ信頼の仕組みとして Signed Query を採用する • ViewModel 廃止検討 Thank you for listening !! 77
  33. #DroidKaigi 2024 GraphQLの魅力を引き出すAndroidクライアント実装 ViewModel を廃止した実装の所感 • 😊 ボイラープレートコードが減る • 🤨

    Recomposition を意識して Query / Mutation を呼び出す必要がある ◦ 画面ごとに Recomposition を意識することになると, 実装難易度とコストが高いので, 適切な共通実装に落とし込むことがポイントになる 91