🦅

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

出典: 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

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.

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キーでエラーの詳細を知ることができる
  • キャッシュ

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
    }
  }
}

ミューテーション

リクエスト

# 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!"
    }
  }
}

サブスクリプション

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
}

その他

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の活用事例

参考