iOSネイティブアプリでalgoliaを使ってみる


なぜalgolia?

アプリなら大体ついてる機能、「キーワード検索」。アプリ側にデータを持たせてfilterやsortを使うのは、めちゃめちゃスペックが落ちるので絶対に避けたいです。
ネイティブアプリのエンジニアであれば、Firebaseを愛用している方も多いかと思いますが、Firebaseでサポートされていないのがクエリの「全文検索」です。指定したキーワード通りの単一・複数ドキュメントをとってきたり、ソートのクエリを追加したり、indexをふるなどはできますが、全文検索はサポートされていません。そこで、公式ドキュメントが推奨する「algolia」を使って、全文検索機能を補填しようと思います。

作るものイメージ

アプリからFirestoreにデータを書き込むと、Cloud Functionsにイベントが発火し、書き込んだ内容をalgoliaに転記するイベントをNode.jsで実装します。algoliaにデータが保存されるので、検索する場合は直接ここにアクセスします。

費用

Cloud Functions自体は無料のSprakプランで使えるのですが、algoliaのようなサードパーティサービスとの連携はBlazeプランが必要です。Blazeプランは無料枠があるので、とりあえずやってみるだけならほぼ費用はかかりません。algoliaも、無料枠があるので、とりあえず、って感じなら総じて無料でできます。
(Blazeプランは従量課金で、通信量を全く管理していなかった結果数100万円の請求がきたという都市伝説を聞くのでお気をつけください・・・)

実装

Firebase側の実装

FirestoreもしくはRealtimeDatabaseから書き込まれた際に、検索対象とするデータをalgoliaに転記するfunctionをNode.jsで実装します。具体的には、下記Firebase公式ドキュメントを参照してください。Nodeの管理にはNVM(NodeVersionManager)が推奨されています。Nodeの環境構築で、NVMをHomebrewでインストールしている記事をちょくちょく見かけますが、NVMの公式ドキュメントによればHomebrewによるインストールは非推奨です。Nodeは使うバージョンがプロジェクトによってコロコロ変わったり、管理が激しいので、ドキュメント通りに構築することを強くお勧めします。
また、Firebaseの公式ドキュメントによればNodeのバージョンは8、10が推奨されていますが、10はベータ版のため8をお勧めします。
CloudFunctionsをデプロイするための環境構築(Firebase公式)

ドキュメント通り進めていき、index.jsファイルを下記の通り実装します。今回は、新規ドキュメントが追加されたらalgoliaに転記するイベントを実装しています。削除時、更新時など、他にも様々な発火条件を設定できます。

index.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const algoliasearch = require('algoliasearch')
const ALGOLIA_ID = functions.config().algolia.app_id
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key
const ALGOLIA_SEARCH_KEY = functions.config().algolia.search_key
const ALGOLIA_INDEX_NAME = 'sampleDemo'
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY)

//発火タイミングを指定(今回はonCreate)、発火する場所(コレクション名等)を指定
exports.onProductUpdated = functions.firestore.document('コレクション名/{id}').onCreate((snap, context) => {
const data = snap.data()
data.objectID = context.params.id
// algoliaのindexへ追加
const index = client.initIndex(ALGOLIA_INDEX_NAME)
return index.saveObject(data)
})

アプリ側の実装

CocoaPodか、Carthageでインストールできます。インストールしたら、ソースコードに「InstantSearchClient」をimportしてください。
本来はAPIクライアントを作成してエラーハンドリングをしますが、今回は、とりあえず動かすためのコードでJSONのデコードまでをやります。JSONのサンプルレスポンスも公式に上がっているので、お好みに合わせてどうぞ。

ViewController.swift
import UIKit
import InstantSearchClient

class ViewController: UIViewController {

    let appId = "algoliaのアプリID"
    let apiKey = "algoliaのapiKey"

    override func viewDidLoad() {
        super.viewDidLoad()

        let client = Client(appID: appId, apiKey: apiKey)
        let index = client.index(withName: "sampleDemo")
        let query = Query(query: "semple")

        index.search(query, completionHandler: { (content, error) -> Void in
            if let error = error {
                print(error)
            } else {
                guard let content = content else {
                    fatalError("no content")
                }
                let data = try! JSONSerialization.data(withJSONObject: content, options: .prettyPrinted)
                let response = try! JSONDecoder().decode(Hits.self, from: data)
                print(response)
            }
        })
    }
}
SampleResponse.swift
import Foundation

struct Hits: Codable {
    let hits: [SampleDemo]
}

struct SampleDemo: Codable {
    let title: String
    let content: String
    let objectID: String
}

これで全文検索結果が帰ってきます!
あえて検索キーワード「sample」を「semple」にしてリクエストしていますが、ちゃんと帰ってきました。最高!
タイポは、"typoTolerance"がtrue|falseのパラメータを持っているので、状況に応じてタイポの許容可否は変更できます。

使ってみて

CloudFunctionsの実装は若干慣れが必要ですが、サードパーティサービスとAPI連携できるのは頼もしいです。Firebaseで完結する機能もたくさんあるので、個人開発には向いているかと思います。アプリ側の実装も簡単なので、手ごろに全文検索機能を実装できました。
algoliaは2019年から東京にも拠点を設立しており、今後も楽しみです!!!(早くGoogleが買収してFirebaseに全文検索機能入れろとかいっちゃダメ)

以上