Slide 1

Slide 1 text

Copyright ©2024 Retty, Inc. 
 GraphQL導入への技術選定 
 
 2024/09/10
 
 Retty株式会社 
 松田


Slide 2

Slide 2 text

Copyright ©2024 Retty, Inc. 
 自己紹介
 松田
 Retty株式会社 アプリチーム
 Android/サーバーサイドエンジニア
 2
 X@matsudamper

Slide 3

Slide 3 text

Copyright ©2024 Retty, Inc. 
 サービス紹介 
 3


Slide 4

Slide 4 text

2022.10
 2022.10〜 
 Retty登壇用テンプレート 
 Retty株式会社 名前なまえ 
 Copyright ©2024 Retty, Inc. 
 Copyright ©2024 Retty, Inc. 
 Rettyについて 
 4
 iOS Android Web

Slide 5

Slide 5 text

Copyright ©2024 Retty, Inc. 
 この発表について 
 ● 使用している技術、抱えていた課題 
 ● GraphQLを選定した理由 
 ● 実装するにあたってのライブラリの選定理由 
 ● 導入で得られたメリット 
 5


Slide 6

Slide 6 text

GraphQLを選定した理由
 


Slide 7

Slide 7 text

サービス構成
 マイクロサービス群 アプリバックエンド アプリ ● Kotlin/JVM ● Jersey Android/iOS ● 状態管理はRedux ● Go Protocol Buffers / gRPC REST API Webチームがメイン アプリチームがたまに触る アプリチームがメイン Webチームは殆ど触らない

Slide 8

Slide 8 text

GraphQLを選定した理由
 アプリバックエンド ↔アプリ間での型定義の問題 ● オーバーフェッチを避ける為に複数のユーザー型があった ○ ユーザー画面に使用するユーザー型 ○ 店舗画面の投稿一覧に使用するユーザー型 ○ 投稿画面に使用するユーザー型 ● クライアントの状態管理の Reduxでの問題 ○ ローカルで管理する型にマッピングする処理がサーバー定義の型分ある ○ このReduxのActionではこの値は取得できるんだっけ …?という考慮が発生

Slide 9

Slide 9 text

GraphQLを選定した理由
 コード生成の問題 ● iOS ○ 独自のツールを使用 ○ サーバーコードをリフレクションして、 Swiftのリクエストコードと、レスポンスの型を作成 ○ メンテナンスコスト ■ CocoaPods -> Swift Package Manager対応 ■ 最近はSwift Concurrencyの対応 ● Android ○ サーバーの定義をコピーしてきて使用 ○ サーバーとクライアントの定義がずれる

Slide 10

Slide 10 text

GraphQLを選定した理由
 欲しいもの ● 1つの要素につき1つの型定義 ● 必要な値だけ取得 ● クライアントでの状態/キャッシュ管理 ● コード生成 GraphQL + Apollo Client

Slide 11

Slide 11 text

GraphQL実装の技術選定・調査
 実装する言語を決める ● 今のバックエンドはKotlin。社内の今のバックエンドの標準言語は Go。 ○ 主にGoを使用しているWebチームはアプリバックエンドはほぼ触らない ○ KotlinはAndroidでも使用。iOSのSwiftも言語の設計思想は近い → GoでもKotlinでも良い

Slide 12

Slide 12 text

GraphQL実装の技術選定・調査
 バックエンドを実装する場所を決める ● 既存のバックエンドに統合 ● 新規にサーバーを立ち上げる 既存のバックエンドに統合する事にした ● 新規バックエンドにすると ○ Goを使用して社内のメイン言語と合わせられる ○ 既存バックエンドを一気に廃止しきれない ■ 積極開発されなくなり、開発できる人が減り、メンテナンスが困難になる事が想定される

Slide 13

Slide 13 text

GraphQL実装の技術選定・調査
 graphql.org/community/tools-and-libraries から使えそうなライブラリを探す 選定 ● Spring Boot統合はあるがJersey統合は無い ○ Jersey標準仕様のシンプルな実装なのでそれはそう ● 既存の処理にgraphql-java/graphql-javaを乗せる事にした ○ Spring Boot統合もこれを使用

Slide 14

Slide 14 text

スキーマファースト vs コードファースト 選定理由 ● スキーマをサーバーとクライアントの関心を分ける壁として意識させる ● コードの都合がスキーマに漏れ出ないようにする GraphQL実装の技術選定・調査
 スキーマファーストを選択

Slide 15

Slide 15 text

コード生成ライブラリ kobylynskyi/graphql-java-codegenを使用 ● Kotlinでの生成に対応したのがこのライブラリくらい ● 少しの修正で問題なく使用できた スキーマとコードのマッピング graphql-java-kickstart/graphql-java-toolsを使用 ● 生成したコードをGraphQLに簡単にマッピングできる ● 実装漏れを検出でき、実行時エラーを防げる GraphQL実装の技術選定・調査


Slide 16

Slide 16 text

graphql-java-codegen の修正部分 GraphQL実装の技術選定・調査
 restaurant { id: Id reservationAttributes { ~ } } 親に含まれない内部的な 値を渡したい CompletionStage ↓ CompletionStage>

Slide 17

Slide 17 text

スキーマから生成されたコードの管理 理想 ビルド時にスキーマからコードを生成 ● 編集不可ファイルを間違って書き換えない 現実 iOSは難しい ● ビルド時にコードを生成するのが難しい ● apollo-ios-codegenが生成されたコードを編集して scalar typeのマッピングを行っている GraphQL実装の技術選定・調査


Slide 18

Slide 18 text

セキュリティ GraphQLは何も対策を行わなければ、クライアントから任意のクエリが発行可能 大きいネストしたリクエストを送って、負荷を掛ける事ができる GraphQL実装の技術選定・調査
 restaurant { reports { user { reports { restaurant { ….

Slide 19

Slide 19 text

セキュリティ ● Query depth & Query complexity ○ コストを設定し、大きいリクエストを行えないようにすることが可能 ○ 正規のリクエストもコストに気を配ったりしなければならず面倒 ● Persisted Queries ○ 事前にクエリを登録しておき、クエリの hashだけを送ってリクエストする ○ アプリケーションのリリースビルド時にクエリを CIでAmazon S3に送信し、それをサー バー側で参照 ○ 外部からアクセスできない開発環境はすべてのクエリを許可している GraphQL実装の技術選定・調査


Slide 20

Slide 20 text

余談 検討時に認識していなかった ● Signed Query ○ クエリに署名してサーバー側で署名を検証する ○ Persisted Queriesとの比較 ■ クエリをS3にアップロードする手間が無い ■ Persisted Queriesはhashだけ送るので、通信量が少ない GraphQL実装の技術選定・調査


Slide 21

Slide 21 text

セキュリティ ● Introspection ○ スキーマの構造を返す機能 ○ 開発環境以外では、返さないように設定 ● スタックトレース ○ 何も設定しないとエラーをそのままクライアントに返す ○ 意図せず秘匿情報を返す可能性も ○ サーバー側でエラーを記録し、必要なものだけクライアントに返す用のレスポンスにマッピングし、 他のエラーは返さないように設定 GraphQL実装の技術選定・調査


Slide 22

Slide 22 text

監視 監視にDatadogを使用 ● graphql-javaはJava8の機能まで対応 ○ CompletableFuture.CompletedStageに未対応、全てCompletableFuture.supplyAsyncを使用 GraphQL実装の技術選定・調査


Slide 23

Slide 23 text

監視 リフレクションを使用してInstrumentationでidをトレースに付与 GraphQL実装の技術選定・調査


Slide 24

Slide 24 text

ついでに ● idをLongやString、Int型からUserId等のvalue classにした ○ 他のIdで他のオブジェクトをリクエストするというのを防げる ● offset pagingからcursor pagingにした ○ パフォーマンスが良い実装をするのに都合が良い GraphQL実装の技術選定・調査


Slide 25

Slide 25 text

導入の成果


Slide 26

Slide 26 text

DataLoader ● n+1問題が発生しないように、ほぼ全てのデータを DataLoader経由で取得 ● 実装はgraphql-java/java-dataloaderを使用 ● GraphQLとの統合により、クエリの階層ごとにリクエストがまとめて行われるため、効率よくデータが取 得できる 導入の成果
 


Slide 27

Slide 27 text

override fun name( restaurant: QlRestaurant, env: DataFetchingEnvironment ): CompletionStage> { /** DataLoaderを呼ぶ ** / } override fun status( restaurant: QlRestaurant, env: DataFetchingEnvironment, ): CompletionStage> { /** ~ ** / } override fun tel( restaurant: QlRestaurant, env: DataFetchingEnvironment, ): CompletionStage> { /** ~ ** / } フィールドごとに並列に実行される為、実装を気にせず並列に処理される 例) 導入の成果
 
 restaurant { id: Id name: String status: Status tel: String } GraphQL + DataLoaderで 実行速度が改善

Slide 28

Slide 28 text

表示速度 ● before ○ 店舗の表示に使用している旧 APIは1秒程かかっていた ○ 追加で0.1秒程かかる3つのAPIを呼び出し ● after ○ GraphQLに移行後は全て合わせて 400ms程で取得 ○ 体感できる程早く 導入の成果


Slide 29

Slide 29 text

まとめ ● Rettyアプリでは以下の問題が解決 ○ コードの自動生成による開発効率化 ○ クライアントでのキャッシュ ○ 複雑化した型定義 ○ 表示時間の短縮 チーム構成、技術課題、実装の思想を明確化し、適切な技術選定を 導入の成果
 


Slide 30

Slide 30 text

Thank you! 
 Copyright ©2024 Retty, Inc. 
 ありがとうございました!