shtnkgm
About
Blog
About
Blog
  • iOSとGraphQL

🦅

iOSとGraphQL

@shtnkgm・2021/8/26

iOSアプリ開発者から見たGraphQLについて。 GraphQLについて耳にすることがちらほらあり、詳しく知らなかったのでオライリーの「初めてのGraphQL」を読んだり、Webで調べたことをまとめておく。

出典: 初めてのGraphQL――Webサービスを作って学ぶ新世代API / O'Reilly Japan
https://www.oreilly.co.jp/books/9784873118932/

API通信の歴史について

まず、新世代APIのGraphQLと比較するためにこれまでのAPI通信の歴史において代表的なRPC、SOAP、REST、gRPCについて整理する。

RPC|Remote Procedure Call

  • 1984年、実装方法としてゼロックスが論文を公開: Implementing Remote Procedure Calls
  • ネットワーク越しに別のコンピュータ上のプログラムを呼び出すためのプロトコル ※HTTP前提ではない
  • HTTPを利用したRPCの一種にはXML-RPC(1998〜)、JSON-RPC(2005〜)などがある

出典: Implementing Remote Procedure Calls / Xerox Palo Alto Research Center
http://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf

SOAP|Simple Object Access Protocol

  • 1999年、マイクロソフトなどが提案したプロトコル(XML-RPCから発展)
  • SOAP Version 1.2: https://www.w3.org/TR/soap12-part1/
  • 通信メッセージをSOAPエンベロープ、SOAPヘッダ、SOAPボディをXMLで定義する仕様であり、実装が複雑

例えば、MicrosoftのExchange Web Service(EWS)のクライアントを開発する場合にはSOAPプロトコルでリクエスト/レスポンスパーサーを実装する必要がある。Objective-C、XML、SOAPの組み合わせで実装するのはなかなか大変。

SOAPでのリクエストメッセージ例(参考: EWS クライアント アプリケーションの概要 / Microsoft)

<?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が提唱したアーキテクチャスタイル
  • GET、PUT、POST、DELETEでリソースを操作するリソース指向アーキテクチャ(Resource Oriented Architecture|ROA)
  • URL/URIですべてのリソースを一意に識別
  • メッセージをXMLやJSONでやりとりし、SOAPよりもシンプルに実装できる
  • インタフェース定義言語(IDL)としてSwagger/OpenAPIが利用されることが多い

Swagger specからコード自動生成するためのOSSとして、iOSでは公式のswagger-api/swagger-codegenやXcodeGenやMintの開発者のyonaskolb/SwagGenが利用できる。

出典: Swagger UI / Swagger
https://swagger.io/tools/swagger-ui/

gRPC|gRPC Remote Procedure Call

  • gが何かわからない ※出典
  • 2015年、Googleが開発したRPCフレームワーク
  • https://grpc.io/
  • インタフェース定義言語(IDL)としてProtocol Buffers|protobufを利用
  • Go言語のユーザーに主に使われる、gRPC-Kotlin、gRPC Swiftなどもある

iOSアプリにFirebaseを組み込んだときの依存ライブラリにProtobufが組み込まれるのは内部でgRPCを利用しているためだとわかる。

gRPC Swiftの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/
  • APIのための問い合わせ言語でありランタイム
  • 2012年にFacebookで開発され、2015年に公開

GraphQLを試してみる

  • GraphQLをブラウザで実行したりバリデーションできるツール
    • GraphiQL ※グラフィカルの言葉遊び
    • GraphQL Playground

それぞれのツールの違いはGraphQLの便利なツール / Qiitaがわかりやすい。

これらのツールでサーバーにホスティングしたり、スタンドアロンで簡易実行環境を構築できる。

  • SWAPI(The Star Wars API) - GraphiQL
  • GitHub - GraphiQL

出典: GraphiQL / SWAPI GraphQL API
https://graphql.org/swapi-graphql

GraphQLのメリット

  • クライアント主導のリクエスト
    • オーバーフェッチ / アンダーフェッチを防ぎパフォーマンスを向上
    • GraphQLサーバーはBFF層の役割をし、柔軟で高速な開発が可能
  • スキーマによるインタフェースの型定義
    • サーバー/クライアント、マイクロサービス間でのAPI仕様の齟齬をなくす
    • コード自動生成や自動ドキュメント生成が可能

GraphQLのデメリット

デメリットとしてはRESTでは普通にできることがGraphQLでのプラクティスと異なることが挙げられる。

  • エラーハンドリング
    • GraphQLサーバーに到達した場合はHTTPステータスコードは200固定になる
    • クライアントはJSONレスポンスのerrorsキーでエラーの詳細を知ることができる
  • キャッシュ
    • 単一エンドポイントになるためキャッシュデータにユニークIDを振ったり、オブジェクトへのパスなどを利用する
    • https://graphql.org/learn/caching/

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を利用
  • フィールドに重複する部分があればfragmentsで共通化
  • クエリに名前(operation-name)をつけておくことでデバッグやロギングなどに役立つ
  • $変数名 で変数(variables)を定義しクエリの引数にできる、デフォルト値もある
  • directivesを利用すると、@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)

サブスクリプション

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を定義することもできる。

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

Interface

interfaceを定義することもできる。

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)
  • mutationの入力に利用するinput型(input-types)

iOSでのGraphQLクライアントの実装

iOSでのGraphQLクライアントを実装するにはApollo iOSというOSSを利用すると便利。 Apolloでは.graphql拡張子のファイルにクエリを記述する。Apollo iOSを利用すると、.graphqlからSwiftコードを自動生成したり、クライアントキャッシュ機構を利用できたりする。

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
    // 変更後の処理
}

サブスクリプションの実装例

GraphQLの活用事例

  • Yahoo!ショッピング(BE)
    • クーポンページにおいて、BFFサーバーとフロントエンドをGraphQLでやりとり
    • Next.js + NestJS + GraphQLで変化に追従するフロントエンドへ 〜 ショッピングクーポンの事例紹介
  • PayPayフリマ(BE)
    • サーバー間通信においてGraphQLを採用し、変化に強い設計に
    • CtoCフリマアプリの作り方 (バックエンド編) 〜6カ月間のPayPayフリマ開発を支えた設計〜
  • GYAO!(iOS / BE)
    • BFFを用いたREST APIのアーキテクチャから、「GraphQL Gatewayを起点に各マイクロサービスアーキテクチャに接続する構成」に変更
    • GYAO!トップページの表示パフォーマンス改善 〜 GraphQLアーキテクチャへの移行

参考

  • 初めてのGraphQL / オライリージャパン
  • メルカリShops の技術スタックと、その選定理由 / mercari engineering
  • Spring Boot + GraphQLでAPIを作成してみよう! / Yahoo! JAPAN Tech Blog
  • iOS × GraphQLの嬉しみとつらみ / iOSDC2018
  • なぜGraphQLを使うべきではないのか / ITnews
  • GraphQL/REST/gRPCの比較 / Zenn
  • grpc-swiftを使う準備を整える / Qiita
  • Apollo iOSを利用してGraphQL APIと通信する方法 / Mobility Technologies
@shtnkgm
Programmer
@shtnkgmさんをフォロー
About
Blog
Site Policy

© shtnkgm