AWS MobileHub Swift 版 のチュートリアル -NoSQL Database (DynamoDB)編-

  • 4
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Mobile Hub の NoSQL Database (DynamoDB)を使ってみます。
(Mobile Hubってなに? という人はまずこちらをご覧ください。)

設定

Mobile Hub の Configure から、NoSQL Database を選択します。
サンプルのスキーマがいくつか用意されてますので、その中から Notes を選択します。

Screen Shot 2016-06-08 at 9.19.44 AM.png

簡単な DynamoDB の説明

DynamoDBは、NoSQLデータベースです。MongoDBのようにJSONオブジェクトを格納します。
リレーショナルDB的に説明すると、

RDBのレコード

Item 項目 (アマゾンの訳では、項目です。ままアイテムでよくない?)

RDBのカラム

Attribute 属性

RDBのプライマリキー

Partition Key + Sort Key (コンポジットキー)

DynamoDB では、PartitionSort キーを使用して各アイテムを物理ストレージにうまい具合に保持します。
このキーに対してインデックスを作成しクエリーに使用します。

Partitionキーは、ハッシュ値で、どの物理ストレージにアイテムを格納するか決まります。
Sortキーを使用してアイテムをソートして格納します。

サンプルアプリでは、PartitionキーにuserIdを指定してます。ここには、ログインユーザの Cognito IdentityID が入ります。
そして、SortキーにnoteIdを指定してます。

インデックス Secondary Index Queries

プライマリの partition & sort キーとは別に、サブの partition & sort のインデックスをいくつも作成できます。
クエリーを実行するときは、スクショにあるように、インデックスを使用する必要があります。
そうでなければ、全アイテムをガサッと取得し、Swift側でフィルタリング等の処理することになります。

Screen Shot 2016-06-08 at 5.41.35 PM.png

サンプルアプリの説明

基本的には、2つのTableViewControllerで構成されます。左側の画面で、どんなクエリーを投げるか選択します。タップすると、DynamoDBにクエリを投げ、検索結果を右側の画面にリストで表示します。
スクショの例では、Partition Keyが指定の値(userId, Cognito IdentityID)にマッチするアイテムをすべて取得します。
(注: 実行前に、左画面下のInsert Sample Dataをタップして、アイテムをインサートしておきます。)
Screen Shot 2016-06-09 at 8.07.03 AM.png

ざっくり全体像

なんちゃってクラス図で描くとこんなイメージです。 :sweat_drops:
左側のTableView NoSQLTableViewController上で特定のセルをタップすると、didSelectRowAtIndexPath:Dao NotesTablegetItemWithCompletionHandler:を呼び出します。このファンクション内部でDynamoDBにアクセスし、結果データ Entity [Notes]をコールバックで渡します。
prepareForSegue:にて結果データをTableView NoSQLQueryResultViewControllerにセットしてセグエイする。
といった流れになります。
NotesTableがCRUD処理を実装しています。Mobile Hubが、DaoとEntityを、よしなに生成してくれてますので、参考にしてね :grinning: という感じでしょうか?

Screen Shot 2016-06-09 at 7.59.51 AM.png

コードの説明

NoSQLTableViewController

タップ時に呼ばれる tableView:didSelectRowAtIndexPath:の処理です。
何行目のセルがタップされたかみて、それに応じたクエリーをDynamoDBに投げます。
一番上のセルをタップすると、getItemWithCompletionHandler:を呼びます。
これは、プライマリキー検索で、指定した partition & sortキーにマッチするアイテムを一つ返します。
(サンプルではキーはハードコードしてあり、UIから指定はできません。面倒ですし。)
検索結果をブロックで処理し、セグエイを呼びます。

NoSQLTableViewController.swift
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let showQueryResultSeque = "NoSQLShowQueryResultSegue"
        let cell = tableView.cellForRowAtIndexPath(indexPath) as! NoSQLTableCell
        activityIndicator.startAnimating()

        // Get item
        if indexPath.section == 0 {
            table?.getItemWithCompletionHandler?({(response: AWSDynamoDBObjectModel?, error: NSError?) -> Void in
                self.activityIndicator.stopAnimating()
                if let error = error {
                    self.showAlertWithTitle("Error", message: "Failed to load an item. \(error.localizedDescription)")
                }
                else if let response = response {
                    self.performSegueWithIdentifier(showQueryResultSeque, sender: [response])
                }

遷移先の画面 NoSQLQueryResultViewController の、results : [AWSDynamoDBObjectModel]?に、検索結果のリストをセットします。
結果が複数アイテムの場合は、AWSDynamoDBPaginatedOutputを返します。一つの場合は、AWSDynamoDBObjectModel

NoSQLTableViewController.swift
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let indexPath = tableView.indexPathForSelectedRow
        let cell = tableView.cellForRowAtIndexPath(indexPath!) as! NoSQLTableCell

        if let queryResultViewController = segue.destinationViewController as? NoSQLQueryResultViewController {
            queryResultViewController.queryType = cell.queryTypeLabel.text!
            queryResultViewController.queryDescription = cell.queryDescriptionLabel.text!
            queryResultViewController.table = self.table
            if let sender = sender as? AWSDynamoDBPaginatedOutput {
                let paginatedOutput: AWSDynamoDBPaginatedOutput = sender
                queryResultViewController.results = paginatedOutput.items
                queryResultViewController.paginatedOutput = paginatedOutput
            }
            else {
                queryResultViewController.results = (sender as? [AWSDynamoDBObjectModel])
            }
        }
    }

NotesTable

NotesTable クラスは、作成したDynamoDB.Notes テーブルへの Daoクラスになっています。
getItemWithCompletionHandler:completionHandler:で、objectMapper.loadを呼び、DynamoDBにクエリを投げます。
hashKey(partitionキー)とrangeKey(sortキー)に、ログインユーザのCognito IdentityIDと、noteId(ハードコード)をセットします。
検索結果をブロックで処理します。

NotesTable.swift
    func getItemWithCompletionHandler(completionHandler: (response: AWSDynamoDBObjectModel?, error: NSError?) -> Void) {
        let objectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
        objectMapper.load(Notes.self, hashKey: AWSIdentityManager.defaultIdentityManager().identityId!, rangeKey: "demo-noteId-500000", completionHandler: {(response: AWSDynamoDBObjectModel?, error: NSError?) -> Void in
            dispatch_async(dispatch_get_main_queue(), {
                completionHandler(response: response, error: error)
            })
        })
    }

ちなみにこちらのファンクションは、パーティションキーだけを指定して、複数アイテムを取得します。AWSDynamoDBPaginatedOutput
内部でobjectMapper.queryを呼んでます。

    func queryWithPartitionKeyWithCompletionHandler(completionHandler: (response: AWSDynamoDBPaginatedOutput?, error: NSError?) -> Void) {
        let objectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
        let queryExpression = AWSDynamoDBQueryExpression()

        queryExpression.keyConditionExpression = "#userId = :userId"
        queryExpression.expressionAttributeNames = ["#userId": "userId",]
        queryExpression.expressionAttributeValues = [":userId": AWSIdentityManager.defaultIdentityManager().identityId!,]

        objectMapper.query(Notes.self, expression: queryExpression, completionHandler: {(response: AWSDynamoDBPaginatedOutput?, error: NSError?) -> Void in
            dispatch_async(dispatch_get_main_queue(), {
                completionHandler(response: response, error: error)
            })
        })
    }

まとめ

DynamoDBのスキーマ変更をする場合、Mobile Hubのコンソール画面から再度Dao、Entityをジェネレートする必要があります。
Teraformなどでプロビジョニングを自動化できれば良いですが、まだそのあたりが整備されてないようです。

また、Daoクラスが、汎用性のある作りではなく、あくまでサンプルコードですので、修正をした場合、Mobile Hubに上書きされてしまいます。
プロトコル拡張でもう少し汎用的な作りにできないかなとか感じました。