iOSアプリからAWS RDSに対し任意のSQLで問い合わせを行う方法

AWS上のRDS(MySQL)にiOSアプリからアクセスするための手順です。

すべての作業をスムーズに進めるため、次の作業を終えている必要があります。

+ AWSアカウントの作成
+ Python2.7、pipのインストール
+ CocoaPodsのインストール

また、AWSで行うすべての作業が同一リージョンで行われることを前提としています。

--------
### AWSでRDSインスタンスを作成する
AWSコンソールのRDSにアクセスし、`DBインスタンスの起動`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/ead01488-462b-46ac-c6cf-3e0089da5b53.jpeg" width=50%>

エンジンは`MySQL`を選択し、`次へ`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/1b9c7365-fe55-4bbd-5cd0-cd492a57b285.jpeg" width=50%>

ユースケースは`開発/テスト`を選択し、`次へ`をクリックします。
*※使用する環境に適したユースケースを設定してください。*
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/54f1d2a6-666f-2d47-2560-3c4f1ad09a9c.jpeg" width=50%>

DB詳細の指定では最下部にある`設定`の各項目に入力します。
`DBインスタンス識別子`には`SampleInstance`と入力します。
`マスターユーザの名前`及び`マスターパスワード`にはDBにアクセス可能なユーザ情報を入力します。
マスターユーザのアカウント情報は後の作業使うため、メモに残します。
入力を終えたら`次へ`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/6639ecbd-4ed8-56a1-57b7-037cb9911796.jpeg" width=50%>


[詳細設定]の設定を行います。
ネットワーク&セキュリティの各項目を下図のように設定します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/9f683590-0661-cdde-b34a-71e63719cb6e.png" width=50%>

データベースの設定では`データベースの名前`に`SampleDB`と入力します。
データベース名は後の作業で使うため、メモに残します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/3f3a63f7-b458-1e2d-ddfa-73c85731265c.jpeg" width=50%>

その他項目は初期状態のままで問題ありません。
入力が完了したら最下部にある`DBインスタンスの作成`をクリックします。

処理が完了するまで暫く待ちます。
処理中に`DBインスタンスの詳細の表示`をクリックしても問題ありません。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/1ef47266-44ba-30ba-a052-5a9f4dba570a.png" width=50%>

DBインスタンスの詳細画面の要約にある`DBインスタンスのステータス`が利用可能であれば、正常にDBが作成されています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/63073964-7127-1826-49eb-7dbb848e9194.jpeg" width=50%>

セキュリティグループのインバウンド制約を変更し、Lambda関数からアクセスできるようにします。
セキュリティグループをクリックし、管理画面を表示します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/8784dcba-5032-83d7-7631-034d53ac5d83.jpeg" width=50%>

画面下部にある`インバウンド`タブをクリックし、`編集`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/1d46c65a-90a2-1374-14e5-c562c80a84bf.jpeg" width=50%>

ソース欄のIPアドレスを`0.0.0.0/0`に変更し、`保存`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/68a8d0f7-21f5-13a8-f80d-d750cbd6253f.jpeg" width=50%>

セキュリティグループの管理画面を終了します。

最後にDBインスタンスの詳細画面から今後使用する情報をメモに残します。

+ VPC
+ サブネットグループ
+ サブネット
+ セキュリティグループ
+ エンドポイント

<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/6b33cc09-45f4-5e19-c95d-a824979e66fd.jpeg" width=50%>

--------
### Mobile HubでiOSアプリを追加する
AWSコンソールのMobile Hubにアクセスし、`Create`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/81dfbc73-5076-c2f6-c27f-46f8ebc259b3.jpeg" width=50%>

プロジェクト名に`SampleApp`と入力し、`Next`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/b9c6f690-133f-e75d-afc0-15e4d36ce116.jpeg" width=50%>

プラットフォームは`iOS`を選択し、`Add`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/333a0707-f2e8-5dd5-1919-1138a58772cd.jpeg" width=50%>

Set up your backendでは何も行わずに`Next`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/e0abedc0-a283-ac13-8722-caa927b22322.jpeg" width=50%>

Connect to your backendでは何も行わずに`Done`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/59389f6b-0bae-71ec-6db7-2f4ec93c8d0e.jpeg" width=50%>

SampleAppの設定画面が表示されます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/a690a38c-d823-51da-9a4c-623723ead15c.png" width=50%>

SampleAppの設定画面下部Add More Backend Features中の`Cloud Logic`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/dee6a59e-64b0-eff4-77b3-6a4fa2e20d97.jpeg" width=50%>

`Create new API`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/7e304c36-b270-7847-a20d-033d140936e8.jpeg" width=50%>

API nameに`ExecuteSQL`と入力し、`Create API`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/361a11c6-0757-c75a-15c7-7fddfec9d7d1.jpeg" width=50%>

暫く待ちます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/17a2aad3-727e-ad4c-f497-02e0075630ed.png" width=50%>

この状態になるとAPIの作成完了です。
自動的にLambda及びAPI Gatewayにそれぞれ処理が追加されています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/8c7758a4-1754-f041-6a2f-6903f9f02deb.png" width=50%>

--------
### Lambdaのデプロイパッケージを作成する
ローカル環境でLambdaにデプロイするためのパッケージを作成します。
環境はPython2.7を用います。
まず何れかの場所に作業フォルダを作成します。

テキストエディットで新規書類を作成し、次のコードを入力します。
`"エンドポイント"` `"マスターユーザ名"` `"マスターユーザパスワード"` `"データベース名"`はMySQLインスタンス作成時にメモした情報に置き換えます。

```
host_url = "エンドポイント"
db_username = "マスターユーザ名"
db_password = "マスターユーザパスワード"
db_name = "データベース名"
```

入力を終えたら`rds_config.py`という名称で作業フォルダに保存します。

再度テキストエディットで新規書類を作成し、次のコードを入力します。
注意：Pythonはインデントが **超重要** な言語です。入力の際はインデントがズレないように気をつけます。

```
import sys
import logging
import rds_config
import pymysql
import json

rds_host  = rds_config.host_url
name = rds_config.db_username
password = rds_config.db_password
db_name = rds_config.db_name

logger = logging.getLogger()
logger.setLevel(logging.INFO)

try:
    conn = pymysql.connect(rds_host, user=name, passwd=password, db=db_name, connect_timeout=5)
except:
    logger.error("ERROR: Unexpected error: Could not connect to MySql instance.")
    sys.exit()

logger.info("SUCCESS: Connection to RDS mysql instance succeeded")
def handler(event, context):

    if ("body" in event):
        param = event["body"]
        if (isinstance(param, unicode)):
            paramDic = json.loads(param)
        else:
            paramDic = param
        
        if ("sql" in paramDic):
            sql = paramDic["sql"].strip()
            logger.info(sql)
            if sql.upper().find("SELECT") == 0:
                with conn.cursor(pymysql.cursors.DictCursor) as cur:
                    cur.execute(sql)
                    data = cur.fetchall()

                    result = {
                        "body": json.dumps(data)
                    }

            else:
                with conn.cursor() as cur:
                    cur.execute(sql)
                    conn.commit()

                    result = {
                        "body": "executed: " + sql
                    }

        else:
            result = {
                "body": """ "sql" param not exsists. """
            }

    else:
        result = {
            "body": "body is not exists"
        }

    return result
```

入力を終えたら`app.py`という名称で作業フォルダに保存します。

pipコマンドでpymysqlライブラリを作業フォルダにインストールします。
コンソールを開き、次のコマンドを実行します。

```
$pip install PyMySQL -t 作業フォルダのパス
```

Finderで作業フォルダ内を確認します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/c0ded0bc-d22c-573b-205a-c0977d771a82.png" width=50%>

作業フォルダ内に準備した4つのファイルとフォルダを圧縮します。
圧縮ファイル名は`app.zip`とします。
注意：作業フォルダごと圧縮しないよう気をつけてください。

--------
### Lambda関数の実行環境を設定する
AWSコンソールのAWS Lambdaにアクセスし、画面左にある`関数`をクリックします。
表示された関数一覧にMobile Hubにより自動作成された関数があることを確認します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/eb85937c-3996-7c26-751f-fe5cc48cf984.jpeg" width=50%>

関数名をクリックし、詳細画面を表示します。
関数コードの各項目を次のように変更します。

+ コードエントリタイプを`.zip ファイルをアップロード`に変更
+ ランタイムを`Python2.7`に変更
+ ハンドラを`app.handler`に変更

<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/e5ec3bc8-fba7-74a8-5b15-cffd7f63ce63.png" width=50%>

関数パッケージ`アップロード`をクリックし、先程作成した`app.zip`を選択します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/e4b5726a-5cd2-463c-807b-17a24a1e05be.jpeg" width=50%>

ネットワークのVPCにメモに残しているVPCを設定します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/d9c8df0a-896d-8397-db96-c3e0227e3a8c.jpeg" width=50%>

続けてサブネットとセキュリティグループもメモを元に設定します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/c7937e6e-536a-86f4-0ab3-1ed93e02f793.jpeg" width=50%>

画面右上の`保存`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/9b296e04-6272-bc7c-0a1d-fb307f4b4e2b.jpeg" width=50%>

保存された旨が表示されれば設定完了です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/fa7a346e-8595-b80b-f56a-76e415e85d0f.png" width=50%>

--------
### サンプルアプリを実行
Mobile Hubからサンプルアプリのプロジェクトファイルをダウンロードし、正しくSQL問い合わせができることを確認します。
AWSコンソールのMobile Hubにアクセスし、`SampleApp`を選択します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/4c208b7f-934f-badb-24a1-da2ee3840483.jpeg" width=50%>

SampleApp詳細画面右上にある`Resources`をクリックします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/5d21afaf-1829-1d33-fc7c-67de1e796b66.jpeg" width=50%>

`Download`をクリックし、表示されるドロップダウンでは`iOS Swift`を選択します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/2e12d7ea-9496-d4f5-5c48-c137bf904536.jpeg" width=50%>

`SampleApp-aws-my-sample-app-ios-swift.zip`がダウンロードされるので、任意の場所で展開します。

コンソールを開き、以下のコマンドで関連ライブラリをインストールします。

```
$cd 展開したフォルダ内の podfile までのパス
$pod install
```

インストールが完了したら`MySampleApp.xcworkspace`を開き、実行します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/70ed0cd5-b9d2-b8ea-b050-dda9cf5eaa99.png" width=50%>

--------
### サンプルアプリで動作確認
アプリトップ画面に表示されている`Cloud Logic`をタップします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/9d580cf5-c2c8-33a4-5113-3889ddc7f5c7.jpeg" width=50%>

案内画面下部にある`Demo Cloud Logic`をタップします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/9039fad6-37c1-a5e1-273e-ee7293d91eda.jpeg" width=50%>

関数一覧画面では`ExecuteSQL`をタップします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/de762ebc-6885-2d51-141f-2a8181aaaf7e.jpeg" width=50%>

Select Method一覧にある`POST`をタップします。
<img src="https://qiita-image-store.s3.amazonaws.com/0/4077/30d480a1-f470-c3a8-39b4-758ec066f0bc.jpeg" width=50%>

Request Bodyの値を書き換え、`Invoke`をタップすることで結果がResponseに表示されます。
一連のタスクを実行し、iOSアプリからAWS RDSに対して問い合わせが行われることを確認します。

Request bodyその1

```
{
  "sql" : "create table Employee3 ( EmpID  int NOT NULL, Name varchar(255) NOT NULL, PRIMARY KEY (EmpID))"
}
```

Response

```
executed: create table Employee3 ( EmpID  int NOT NULL, Name varchar(255) NOT NULL, PRIMARY KEY (EmpID))
```

Request bodyその2

```
{
  "sql" : "insert into Employee3 (EmpID, Name) values(1, 'Joe')"
}
```

Response

```
executed: insert into Employee3 (EmpID, Name) values(1, 'Joe')
```

Request bodyその3

```
{
  "sql" : "insert into Employee3 (EmpID, Name) values(2, 'Bob')"
}
```

Response

```
executed: insert into Employee3 (EmpID, Name) values(2, 'Bob')
```

Request bodyその4

```
{
  "sql" : "insert into Employee3 (EmpID, Name) values(3, 'Mary')"
}
```

Response

```
executed: insert into Employee3 (EmpID, Name) values(3, 'Mary')
```

Request bodyその5

```
{
  "sql" : "select * from Employee3"
}
```

Response

```
[{\"EmpID\": 1, \"Name\": \"Joe\"}, {\"EmpID\": 2, \"Name\": \"Bob\"}, {\"EmpID\": 3, \"Name\": \"Mary\"}]
```