ElasticsearchとSwiftで簡単mBaaSサンプルアプリ

# はじめに

この記事は `Elasticsearch`と`Swift`のAdvent Calendarを同時に攻略しようとする一石二鳥な実験的記事です。

`Elasticsearch Advent Calendar 2014 4日目`と`Swift Advent Calendar 2014 4日目`を兼ねています。


# 概要

解析用サーバとしての利用が多いElasticsearchの「データストア」としての利用を促すために、最近流行りのmBaaSサーバとして簡単に構築してみました。

全てのロジックはSwift側で実装しており、Elasticsearchへは直接アクセスしています。

なので、本番環境としての利用はセキュリティ的におすすめしません。
実際に使うためには、SwiftとElasticsearchの間にWebアプリケーションを挟んで認証をショートカットされないようにする必要があります。
（ElasticsearchのPlugin作成が可能ならWebアプリケーションを利用せずに完結することも可能です。）

![Screen Shot 2014-12-04 at 4.46.39.png](https://qiita-image-store.s3.amazonaws.com/0/10272/1d0bd5f0-3230-81cc-6f49-b5d647194bcb.png)


Elasticsearchの認証部分に[elasticsearch-auth](https://github.com/codelibs/elasticsearch-auth)を利用しています。

また、Swift側では、[Alamofire](https://github.com/Alamofire/Alamofire) と[SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)を利用しています。

この実験に使ったコードは[tadyjp/ElasticSwift](https://github.com/tadyjp/ElasticSwift)にあります。


``
$ version
Elasticsearch 1.4.0
Swift version 1.1 (swift-600.0.56.1)
```


![Screen Shot 2014-12-04 at 5.04.04.png](https://qiita-image-store.s3.amazonaws.com/0/10272/7c671e60-e04e-c734-dc63-fc45ef806ca2.png)

![Screen Shot 2014-12-04 at 5.03.56.png](https://qiita-image-store.s3.amazonaws.com/0/10272/b836446a-c91a-0c58-9a30-e9ddb1e1552b.png)


# Elasticsearchのコード

ありません！

indexの作成や検索は全てSwiftで実装しています。

# Swiftのコード

SwiftでのElasticsearchへの検索は次のコードがメインコードになります。

```swift
        let res = Alamofire.request(.POST, "http://localhost:9200/twitter/tweet/_search?q=user:\(self.username)", parameters: nil, encoding: .JSON)
            .responseJSON { (_, _, json, _) -> Void in
                let sjson = JSON(json!)
                if let hits = sjson["hits"]["hits"].array {
                    var tweets = [] as [String]
                    for subJson: JSON in hits {
                        println(subJson["_source"]["message"].string!)
                        tweets.append(subJson["_source"]["message"].string!)
                    }
                    block(tweets)
                } else {
                    println("getTweets error")
                }
        }
```

単純にJSONリクエストを行っているだけなので難しいところはありません。

```
user:<ユーザー名>
```

という検索クエリを発行することで、自分の投稿のみを抽出しています。


工夫が必要なのはログイン処理の部分です。

```swift

    class func currentUser() -> ElasticUser? {
        let ud = NSUserDefaults.standardUserDefaults()
        if let username = ud.objectForKey("username") as? String {
            let token = ud.objectForKey("token") as String
            let user = ElasticUser(username: username, token: token)
            println("currentUser = \(username)")
            return user
        } else {
            return nil
        }
    }

    class func signUp(username: String, password: String, block: (user: ElasticUser?) -> ()) {
        let params = [
            "authenticator": "index",
            "username": username,
            "password": password,
            "roles": "[\"user\"]"
        ]

        println("signUp...")

        let res = Alamofire.request(.PUT, "http://localhost:9200/_auth/account", parameters: params, encoding: .JSON)
            .responseJSON { (_, _, json, _) -> Void in
                let sjson = JSON(json!)
                println(sjson["status"])
                if sjson["status"] == 200 {
                    self.logIn(username, password: password, { (user2: ElasticUser?) in
                        block(user: user2)
                    })
                }
        }
    }

    class func logIn(username: String, password: String, block: (user: ElasticUser?) -> ()) {
        let params = [
            "username": username,
            "password": password
        ]

        println("logIn...")

        let res = Alamofire.request(.POST, "http://localhost:9200/login", parameters: params, encoding: .JSON)
            .responseJSON { (_, _, json, _) -> Void in
                let sjson = JSON(json!)
                println(sjson["status"])
                if sjson["status"] == 200 {
                    let user: ElasticUser? = ElasticUser(username: username, token: sjson["token"].string!)
                    block(user: user)
                } else {
                    block(user: nil)
                }
        }
    }
```

初回はログイン情報がないのでelasticsearch-authにユーザー作成を依頼します。
すると次回からはこのユーザー名とパスワードでログイン可能になり、一意の`token`が発行されるので、`NSUserDefaults`などに保存して次回以降のアクセス時に利用します。


# まとめ

ちょっとしたクライアントサイドのコードだけでElasticsearchのスキーマレス性を活用したなんちゃってmBaaSが動いてしまいました。

これだけでは趣味の範囲は超えませんが、これをたたき台として、なにか新しいアプリケーションの発想が生まれると幸いです。