AWS
iOS
Swift
GraphQL
GraphQLDay 7

AWS AppSyncを使用したiOSアプリ開発入門

AWS AppSyncとは?

AWSの提供するGraphQLAPIのサービスです。本サービスを導入することでAWSの様々な機能やリソースをバックエンドで使用しながら、クライアント側はクエリ言語で柔軟に取得リソースを定義、取得することが可能になります。
今回はAWSが提供しているiOS向けのライブラリを使用した、iOSクライアント側のGraphQLAPI利用方法について説明します。

構成イメージ図

view.jpg

AppSync側の準備

クライアント実装の前にAWS側でリソースの設定、準備をする必要があります。今回は詳細について解説しませんが、以下のような設定を行っています。

スキーマ設定

type Query {
    getData(userId: ID!): UserData
}

type Mutation {
    putData(
        userId: ID!,
        userName: String,
        height: Int,
        weight: Int
    ): UserData
}

type UserData {
    userId: ID!
    userName: String
    height: Int
    weight: Int
}

type schema {
    query: Query
    mutation: Mutation
}

Getする側のクエリはtype QueryのところでgetDataを定義しています。userIdを指定するとそのuserIdに一致したUserData型を返します。
Postする側のクエリはtype mutationのところでputDataを定義しています。UserData型に必要な値を引数に持ち、API側にsendします。

データソース

DynamoDBを指定しています。今回のデータの格納先です。具体的なAppSync上での関連付けは別記事を参照して下さい。

amplifyを使用したクライアント環境の準備

AppSyncで作成したスキーマ定義をクライアントで設定するにはAWSの提供するamplifyというcliをインストールする必要があります。
AppSyncのコンソールにも手順が書いてありますが、以下のような作業となります。

npmでamplifyをインストールします

$ npm install -g @aws-amplify/cli

今回使用するXCodeプロジェクトを新規作成して下さい。
作成後、プロジェクトのルートディレクトリへ移動し、以下を実行

$ amplify init

実行するとAWSコンソールへブラウザ移動し、Admin権限のIAMユーザー作成を依頼されます。
これは、amplifyがAppSyncを含むフロントエンドサービスの管理リソースを作成するためと思われます。
コマンドを実行するためだけにAdmin権限のユーザーを作成するのは気が引けますが、後で権限の縮小も出来るのでとりあえず作成しましょう。
作成するとS3にamplify用のバケットが作成されます。amplifyの設定ファイル等が置かれると思われますが、今回は特に使用しません。

次に、AppSyncのAPIコンソールでapiIdを確認した後、以下を実行

amplify add codegen --apiId XXXXXXX

対話形式でいくつか答える点がありますが、後でクエリファイルの修正ができるのでまずはYesで問題ないかと思います。
上記手順でXCodeのプロジェクトにAmplifyフォルダとgraphqlフォルダ、awsconfiguration.jsonファイル、そしてAPI.swiftファイルが生成されます。

Graphqlの特徴として、クライアント側でクエリを生成しレスポンスのペイロードを指定することが出来ますが、クエリの作成はgraphqlフォルダのqueries.graphqlファイルで行います。
デフォルトではAppSyncのスキーマで定義したクエリを実行するだけのファイルが自動で生成されますが、このファイルを修正することでクライアントの任意の項目を取得するよう書き換えることが可能です。

次に、生成したファイルはまだXCodeのプロジェクトには含まれていないため、プロジェクト内でダブルクリックしAdd Files to "プロジェクト名"で取り込んで下さい。

AppSyncライブラリのインポート

XCodeではAmplifyでの設定ファイル作成の他に、Cocoapodsを使用したAppSyncライブラリのインポートが必要です。XCodeプロジェクトのルートディレクトリでpodfileを作成し、以下を追記してインストールして下さい。

target 'PostsApp' do
    use_frameworks!
    pod 'AWSAppSync', ' ~> 2.6.24'
end

このライブラリはiOSのGraphQLライブラリであるApolloをベースとしており、使い勝手もかなり近いものとなっています。

iOSネイティブアプリの実装

やっと準備が出来ました。しかし、まだ設定は終わっていません。
AppDelegate.swiftに以下を記述しAppSyncClientを初期化しましょう。

AppDelegate.swift
import AWSAppSync

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var appSyncClient: AWSAppSyncClient?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    //You can choose your database location
    let databaseURL = URL(fileURLWithPath:NSTemporaryDirectory()).appendingPathComponent("database_name")

    do {
        //AppSync configuration & client initialization
        let appSyncConfig = try AWSAppSyncClientConfiguration(appSyncClientInfo: AWSAppSyncClientInfo(),databaseURL: databaseURL)
        appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig)
        // Set id as the cache key for objects. See architecture section for details
        appSyncClient?.apolloClient?.cacheKeyForObject = { $0["id"] }
        } catch {
            print("Error initializing appsync client. \(error)")
        }
        //other methods
        return true
}

databaseURLで指定している"database_name"の部分にはAppSyncコンソールで設定しているデータベース名を指定するようにしましょう。
また、appSyncConfigにAWSAppSyncClientInfo()で内容がセットされていますが、ここではawsconfigure.jsonの値が参照されています。

クエリの実行

それでは作成されているクエリを元にAPIを実行させましょう。
まずはViewControllerでAppDelegateのAppSyncClientを取得して下さい。

ViewController.swift
import AWSAppSync

class Todos: UIViewController{
    //Reference AppSync client
    var appSyncClient: AWSAppSyncClient?

    override func viewDidLoad() {
        super.viewDidLoad()
        //Reference AppSync client from App Delegate
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appSyncClient = appDelegate.appSyncClient
    }
}

クエリの実行はAppSyncClientのfetchで行います。引数にクエリを指定し、クロージャでレスポンスを取得します。

    appSyncClient?.fetch(query: getData(userId: "id"))  { (result, error) in
    if error != nil {
        print(error?.localizedDescription ?? "")
        return
    }
    print(result?.data?.getData?.userName)
}

mutationの実行

APIへのpostリクエストはAppSyncのmutationで定義した関数を使用します。AppSyncClientではperformというメソッドでmutationを指定し、実行することができます。

let postMutation = putData(userId: "id", userName: "name", height: "170", weight: "70")

appSyncClient?.perform(mutation: postMutation) { (result, error) in
    if let error = error as? AWSAppSyncClientError {
        print("Error occurred: \(error.localizedDescription )")
    }
    if let resultError = result?.errors {
        print("Error saving the item on server: \(resultError)")
        return
    }
}

終わりに

AppSyncは昨年登場した新しいサービスですが、クライアント側でGraphqlクエリを容易に編集し反映できる点が良いと感じました。
またフロントとバックエンドのやり取りも最小限で済むためメリットの多いサービスだと感じています。