iOSとGraphQL
iOSアプリ開発者から見たGraphQLについて。 GraphQLについて耳にすることがちらほらあり、詳しく知らなかったのでオライリーの「初めてのGraphQL (opens new window)」を読んだり、Webで調べたことをまとめておく。
出典: 初めてのGraphQL――Webサービスを作って学ぶ新世代API / O'Reilly Japan
https://www.oreilly.co.jp/books/9784873118932/ (opens new window)
API通信の歴史について
まず、新世代APIのGraphQLと比較するためにこれまでのAPI通信の歴史において代表的なRPC、SOAP、REST、gRPCについて整理する。
RPC|Remote Procedure Call
- 1984年、実装方法としてゼロックスが論文を公開: Implementing Remote Procedure Calls (opens new window)
- ネットワーク越しに別のコンピュータ上のプログラムを呼び出すためのプロトコル ※HTTP前提ではない
- HTTPを利用したRPCの一種にはXML-RPC (opens new window)(1998〜)、JSON-RPC (opens new window)(2005〜)などがある
出典: Implementing Remote Procedure Calls / Xerox Palo Alto Research Center
http://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf (opens new window)
SOAP|Simple Object Access Protocol
- 1999年、マイクロソフトなどが提案したプロトコル(XML-RPCから発展)
- SOAP Version 1.2: https://www.w3.org/TR/soap12-part1/ (opens new window)
- 通信メッセージをSOAPエンベロープ、SOAPヘッダ、SOAPボディをXMLで定義する仕様であり、実装が複雑
例えば、MicrosoftのExchange Web Service(EWS) (opens new window)のクライアントを開発する場合にはSOAPプロトコルでリクエスト/レスポンスパーサーを実装する必要がある。Objective-C、XML、SOAPの組み合わせで実装するのはなかなか大変。
SOAPでのリクエストメッセージ例(参考: EWS クライアント アプリケーションの概要 / Microsoft (opens new window))
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestServerVersion Version="Exchange2007_SP1" />
</soap:Header>
<soap:Body>
<GetFolder xmlns="https://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
<FolderShape>
<t:BaseShape>Default</t:BaseShape>
</FolderShape>
<FolderIds>
<t:DistinguishedFolderId Id="inbox"/>
</FolderIds>
</GetFolder>
</soap:Body>
</soap:Envelope>
REST|Representational State Transfer
- 2000年、カルフォルニア大学のRoy Fielding (opens new window)が提唱したアーキテクチャスタイル
- GET、PUT、POST、DELETEでリソースを操作するリソース指向アーキテクチャ(Resource Oriented Architecture|ROA)
- URL/URIですべてのリソースを一意に識別
- メッセージをXMLやJSONでやりとりし、SOAPよりもシンプルに実装できる
- インタフェース定義言語(IDL)としてSwagger/OpenAPI (opens new window)が利用されることが多い
Swagger specからコード自動生成するためのOSSとして、iOSでは公式のswagger-api/swagger-codegen (opens new window)やXcodeGen (opens new window)やMint (opens new window)の開発者のyonaskolb/SwagGen (opens new window)が利用できる。
出典: Swagger UI / Swagger
https://swagger.io/tools/swagger-ui/ (opens new window)
gRPC|gRPC Remote Procedure Call
- gが何かわからない ※出典 (opens new window)
- 2015年、Googleが開発したRPCフレームワーク
- https://grpc.io/ (opens new window)
- インタフェース定義言語(IDL)としてProtocol Buffers|protobuf (opens new window)を利用
- Go言語 (opens new window)のユーザーに主に使われる、gRPC-Kotlin (opens new window)、gRPC Swift (opens new window)などもある
iOSアプリにFirebaseを組み込んだときの依存ライブラリにProtobufが組み込まれるのは内部でgRPCを利用しているためだとわかる。
gRPC Swift (opens new window)のprotocコマンドを利用することで、.protoファイルからサーバーサイド、クライアントサイドのSwiftコードを自動生成することができる。
RESTの課題
リソース指向でAPIを定義していくと、リソース指向のデータをUI表示に最適化するための変換を行う上で、以下のような問題が起こりうる。
過剰な取得|オーバーフェッチ
APIレスポンスにはアプリに必要のないデータを含む場合、そのデータを取得することによりバックエンド側で不必要なサービスと連携しなければいけなかったり、通信のパフォーマンスの低下につながる。
過少な取得|アンダーフェッチ
UI/ユースケースの実装に必要なデータが足りず、複数APIのマッシュアップが必要になることがある。
- フロントエンドでAPIをマッシュアップ
- BFF(Backends For Frontends)サーバーでAPIをマッシュアップ
フロントエンドでのAPIのマッシュアップには以下の課題がある。
- 直列での複数APIリクエストによる通信レスポンス(Round-Trip Time|RTT)
- 並列での複数APIリクエストによる処理の複雑化(非同期処理の待ち合わせ、スレッドセーフな仕組みが必要)
- iOS、Android、Webなど各フロントエンドにビジネスロジックが分散する
一方、BFFサーバーでのマッシュアップもビジネスロジックが集中することにより、その複雑性をうまく処理する必要があったり、日々変化するフロントエンドの要求に応じて、開発リソースを割く必要がある。
GraphQLとは
GraphQL is a query language for your API, and a server-side runtime for executing queries using a type system you define for your data.
- 公式サイト: https://graphql.org/ (opens new window)
- APIのための問い合わせ言語でありランタイム
- 2012年にFacebookで開発され、2015年に公開
GraphQLを試してみる
- GraphQLをブラウザで実行したりバリデーションできるツール
それぞれのツールの違いはGraphQLの便利なツール / Qiita (opens new window)がわかりやすい。
これらのツールでサーバーにホスティングしたり、スタンドアロンで簡易実行環境を構築できる。
出典: GraphiQL / SWAPI GraphQL API
https://graphql.org/swapi-graphql (opens new window)
GraphQLのメリット
- クライアント主導のリクエスト
- オーバーフェッチ / アンダーフェッチを防ぎパフォーマンスを向上
- GraphQLサーバーはBFF層の役割をし、柔軟で高速な開発が可能
- スキーマによるインタフェースの型定義
- サーバー/クライアント、マイクロサービス間でのAPI仕様の齟齬をなくす
- コード自動生成や自動ドキュメント生成が可能
GraphQLのデメリット
デメリットとしてはRESTでは普通にできることがGraphQLでのプラクティスと異なることが挙げられる。
- エラーハンドリング
- GraphQLサーバーに到達した場合はHTTPステータスコードは200固定になる
- クライアントはJSONレスポンスのerrorsキーでエラーの詳細を知ることができる
- キャッシュ
- 単一エンドポイントになるためキャッシュデータにユニークIDを振ったり、オブジェクトへのパスなどを利用する
- https://graphql.org/learn/caching/ (opens new window)
GraphQLのリクエストの種類
- クエリ・・・データの取得
- ミューテーション・・・データの更新
- サブスクリプション・・・データの監視
それぞれがGraphQLではQuery
型、Mutation
型、Subscription
型のルート型として定義される。
クエリ
リクエスト
# idが1000のhumanのnameとheightを取得
# query {}は簡易表記として省略もできる
query {
{
human(id: "1000") {
name
height(unit: FOOT) #単位をフィートに
}
}
}
レスポンス
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}
- 返却するフィールド名を変更する場合はaliases (opens new window)を利用
- フィールドに重複する部分があればfragments (opens new window)で共通化
- クエリに名前(operation-name (opens new window))をつけておくことでデバッグやロギングなどに役立つ
$変数名
で変数(variables (opens new window))を定義しクエリの引数にできる、デフォルト値もある- directives (opens new window)を利用すると、
@include(if: Boolean)
や@skip(if: Boolean)
などで結果をフィルタリングしたりできる
ミューテーション
リクエスト
# CreateReviewForEpisodeは操作名
# Mutation型のcreateReviewというフィールドを指定し、レビューを新規作成する
# 変更後の値として、starsとcommentaryを取得する
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
レスポンス
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
- 複数のフィールドを指定した場合、クエリは並列実行、ミューテーションは直列実行になる(multiple-fields-in-mutations (opens new window))
サブスクリプション
WebSocketを通じてリアルタイムにデータの更新情報を受け取ることができる。
# Subscription型のliftStatusChange(Lift型)を監視し、変更があったらname、capacity、statusを取得
subscription {
liftStatusChange {
name
capacity
status
}
}
GraphQLスキーマ
Object Type
type Character {
name: String!
appearsIn: [Episode!]!
}
上記のスキーマは以下を表す
- Character型(オブジェクト)
- String型のnameとEpisode型の配列のappearsInをフィールドに持つ
- !はnon-nullable
引数をとるtypeの書き方
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
Scalar Type
デフォルトのスカラー型。日付など、独自のスカラー型を定義することもできる。。
- Int(符号付き32bit整数)
- Float(符号付き倍精度浮動小数)
- String
- Boolean
- ID(一意の識別子、Stringと同じ方法でシリアライズされる)
Enum
enum (opens new window)を定義することもできる。
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
Interface
interface (opens new window)を定義することもできる。
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
その他
- 異なる型を意味する
union SearchResult = Human | Droid | Starship
のようなユニオン型(union-types (opens new window)) - mutationの入力に利用する
input
型(input-types (opens new window))
iOSでのGraphQLクライアントの実装
iOSでのGraphQLクライアントを実装するにはApollo iOS (opens new window)というOSSを利用すると便利。 Apolloでは.graphql拡張子のファイルにクエリを記述する。Apollo iOSを利用すると、.graphqlからSwiftコードを自動生成したり、クライアントキャッシュ機構 (opens new window)を利用できたりする。
import Apollo
import ApolloWebSocket
let url = URL(string: "https://api.sample.com/graphql")!
let client = ApolloClient(url: url)
// クエリ
client.fetch(query: SomeQuery()) { result, error in
// 取得後の処理
}
// ミューテーション
client.perform(mutation: SomeMutation()) { result in
// 変更後の処理
}
サブスクリプションの実装例 (opens new window)
GraphQLの活用事例
- Yahoo!ショッピング(BE)
- クーポンページにおいて、BFFサーバーとフロントエンドをGraphQLでやりとり
- Next.js + NestJS + GraphQLで変化に追従するフロントエンドへ 〜 ショッピングクーポンの事例紹介 (opens new window)
- PayPayフリマ(BE)
- サーバー間通信においてGraphQLを採用し、変化に強い設計に
- CtoCフリマアプリの作り方 (バックエンド編) 〜6カ月間のPayPayフリマ開発を支えた設計〜 (opens new window)
- GYAO!(iOS / BE)
- BFFを用いたREST APIのアーキテクチャから、「GraphQL Gatewayを起点に各マイクロサービスアーキテクチャに接続する構成」に変更
- GYAO!トップページの表示パフォーマンス改善 〜 GraphQLアーキテクチャへの移行 (opens new window)
参考
- 初めてのGraphQL / オライリージャパン (opens new window)
- メルカリShops の技術スタックと、その選定理由 / mercari engineering (opens new window)
- Spring Boot + GraphQLでAPIを作成してみよう! / Yahoo! JAPAN Tech Blog (opens new window)
- iOS × GraphQLの嬉しみとつらみ / iOSDC2018 (opens new window)
- なぜGraphQLを使うべきではないのか / ITnews (opens new window)
- GraphQL/REST/gRPCの比較 / Zenn (opens new window)
- grpc-swiftを使う準備を整える / Qiita (opens new window)
- Apollo iOSを利用してGraphQL APIと通信する方法 / Mobility Technologies (opens new window)