every Tech Blog

株式会社エブリーのTech Blogです。

iOSプロジェクトからApolloを削除した話 - GraphQLクライアントの自前実装への移行

はじめに

この記事はevery Tech Blog Advent Calendar 2024の12日目の記事です。

DELISH KITCHENのiOSアプリ開発を担当している池田です。今回はiOSプロジェクトでのGraphQLクライアントをApollo iOSから自前実装へ移行した経験についてお話しします。

背景

DELISH KITCHENのAPIの一部でGraphQLを利用しており、開発効率向上のためにApollo iOSを導入していました。これにより、GraphQLの利用をより簡単に行える環境を整えていました。導入時の詳細については以下の記事をご参照ください。

tech.every.tv

GraphQLについて

GraphQLでは、必要な情報だけを取得できたり複数のエンドポイントのリクエストをひとつにまとめたりできる柔軟なデータ取得が特徴です。クライアント側で必要なデータを宣言的に指定できるため、データの過不足なく効率的な通信が可能になります。一方で、新しい技術仕様の習得やクエリ設計のベストプラクティスの理解など、学習コストが比較的高いことが課題として挙げられます。

Apollo iOSを使う利点

Apollo iOSには主に以下の利点があります:

  • GraphQLのスキーマとクエリからSwiftコードを自動生成できることによる開発効率の向上
  • クライアントサイドでのキャッシュ管理の簡略化とパフォーマンスの最適化
  • 型安全性の保証による実行時エラーの防止

DELISH KITCHENでは、特にコードの自動生成による開発効率向上を目的としてApollo iOSを導入していました。

自前実装の経緯

Apollo iOSは、コード自動生成機能により当初の目的であった開発効率化を実現していました。しかし、DELISH KITCHENのAPIの大半はRESTfulで、GraphQLの利用は限定的であったため、実際の効率化の効果は想定を下回っていました。

さらに、今後もGraphQLの利用を積極的に拡大しない方針が決定されたことで、Apollo iOSを維持するコストが相対的に高くなってきました。具体的には以下の課題が浮き彫りになりました:

  • iOSプロジェクトのApollo iOSへの依存関係の管理
  • コード自動生成に必要な非Swiftファイルの維持
  • 自動生成されたファイルによるプロジェクト管理上の複雑さ

これらの状況を踏まえ、Apollo iOSへの依存を解消し、必要最小限の機能に特化したGraphQLクライアントを自前で実装することを決定しました。

GraphQLクライアントの自前実装

GraphQLは本質的にはHTTP POSTリクエストであり、適切なリクエストボディを構築することで実装が可能です。ここでのクエリ文字列自体はApollo iOS使用時と同様にスキーマを元に構築する必要があります。以下に基本的な実装例を示します:

// GraphQLのエンドポイントURL
let url = URL(string: "https://api.example.com/graphql")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

// スキーマを元に構築したGraphQLのクエリGraphQLのクエリ
let query = """
    mutation CreateOrder($productId: ID!, $quantity: Int!, $shippingAddress: AddressInput!) {
      createOrder(productId: $productId, quantity: $quantity, shippingAddress: $shippingAddress) {
        orderId
        totalPrice
        estimatedDeliveryDate
        status
      }
    }
    """

// 変数の定義
let variables: [String: Any] = [
    "productId": "prod_123456",
    "quantity": 2,
    "shippingAddress": [
        "street": "123 Main St",
        "city": "Tokyo",
        "postalCode": "100-0001",
        "country": "Japan"
    ]
]

// リクエストボディの構築
let body: [String: Any] = [
    "query": query,
    "variables": variables
]

// リクエストの実行
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: body)

do {
    let (data, response) = try await URLSession.shared.data(for: urlRequest)
    
    // レスポンス処理
    if let httpResponse = response as? HTTPURLResponse {
        switch httpResponse.statusCode {
        case 200:
            let json = try JSONSerialization.jsonObject(with: data)
            print("注文が成功しました:", json)
        default:
            print("エラーが発生しました。ステータスコード:", httpResponse.statusCode)
        }
    }
} catch {
    print("リクエストエラー:", error)
}

このように、GraphQLクライアントの基本的な機能は標準的なネットワーキング処理で実装することができます。実際のプロジェクトでは、この基本実装をベースに、既存のRESTful APIクライアントと同じインターフェースで利用できるよう設計し、ネットワーキング層の実装を共通化しました。

おわりに

今回は、Apollo iOSから自前実装への移行について紹介しました。GraphQLの利用範囲や開発方針に応じて、時にはサードパーティライブラリへの依存を見直し、シンプルな実装へ移行することも選択肢のひとつとなり得ることが分かりました。

この記事が、同様の課題に直面している開発者の方々の参考になれば幸いです。