まえがき
Firebase Advent Calendar 2020 を覗いてみると本日12/14日に空白があり、悲しい気持ちになったので急遽当日に Firebase のネタを絞り出してみました👩🌾 12/19も埋まることを願っています!
僕は普段はてなブログで iOS・Android・Firebase 関連の記事を毎日書いているので、暇があればプロフィールページから覗いてみてください🙂
そして、今回はタイトルの通り Firestore のコレクションデータを Algolia に一括でインポートする方法を紹介したいと思います👷♀️間違ってるところや不明点があればぜひ教えてください!
Algolia とは?
Algolia は、高機能な検索APIを提供する SaaS(Software as a Service) です。
Firestore は全文検索をサポートしていないので、 Algolia と組み合わせることでより高度な検索をクライアントから行うことができるようになります。また、全文検索以外にも AI を活用した Algolia AI と呼ばれるものがあるらしく、これらを活用することでより高度な検索体験を実現できるようになると思われます(触る機会があったら別途記事にします)。
また、Firestore のドキュメントでも全文検索のソリューションとして Algolia の使用が大々的に書かれているので、興味のある方は見てみてください。
事前知識
Algolia は元のデータから直接検索を行わず、Algolia サーバーに送信されたデータをもとに検索を行います。そのため、検索に必要となるデータを JSON record に変換して Algolia に送信する必要があります。なお、ここで送信するデータに関しては元のデータ全要素を送信する必要はなく、検索・検索結果として使用する要素のみ含めることができます。
Algolia のデータ構造については、公式ドキュメントに詳しい解説が載っています。
そして今回行う Firestore のコレクションから Algolia へのデータの送信方法は、大きく分けて下記の二つの方法があり、今回は前者の 一括インポート の方法を紹介します🤺
後者の方が実用的ではあり、多くの記事も見かけますが、Algolia を途中から使い始めた場合は元のデータのインポートが必要だったため、この記事を書いてみました👩🌾
-
Firestoreから一括でAlgoliaにデータをインポートする。 -
Firestoreへの書き込み(Create, Update, Delete) をトリガーに、Firebase Functions経由でAlgoliaにデータを逐次インポートする。
それではやっていく🧑🔧
今回は、Firebase CLI を使用して設定したディレクトリのFunctions ディレクトリ内に、インポート用の indexing.js ファイルを作成する感じで実装した手順を紹介します。
-
まずは、
Firebase CLIでプロジェクトを設定します。こちらについては記事の重要な部分ではないため割愛させていただきますが、Firebase の公式ドキュメントを参照すれば簡単にセットアップできます。 -
次に
functionsディレクトリに移動して、インポートに使用するmodulesをインストールします。npm 5.0.0以降からは--saveオプションを付けずともsaveされるようになったようですね。$ cd functions $ npm install algoliasearch $ npm install dotenv $ npm install commander $ npm install firebase
僕をはじめとして、js の module にあまり馴染みのない方向けにそれぞれの役割を書いておきます。
|module|description|link|
|---|---|---|
|algoliasearch|`Algolia` のAPIと通信を行うために使用するモジュールで、ブラウザ・`Node.js` ともに動作します。|https://www.npmjs.com/package/algoliasearch|
|dotenv|`.env` ファイルから環境変数を `process.env` にロードするためのモジュールです。今回の場合は、`algolia` の `Application ID` や `API Key` などの機密性の高い情報を安全に扱うために使用します。ちなみに `Process` は、`Node.js`実行環境のグローバル変数で、`Node.js`の実行プロセスについて、情報の取得・操作を行うためのものです。|https://www.npmjs.com/package/dotenv|
|commander|コマンドライン引数をパースして扱いやすくするために使用します。|https://www.npmjs.com/package/commander#installation|
|firebase|`Firebase` のAPIと通信を行うためのモジュールです。|https://www.npmjs.com/package/firebase|
-
.envファイルに必要な環境変数を設定します。FIREBASE_PROJECT_IDも今回は含めましたが、正直ALGOLIA_APP_IDとALGOLIA_API_KEYが直接ソースコードに書き込まれなければいい気もします🤔ALGOLIA_APP_ID=YOUR_ALGOLIA_APP_ID ALGOLIA_API_KEY=YOUR_ALGOLIA_API_KEY FIREBASE_PROJECT_ID=YOUR_FIREBASE_PROJECT_ID
それぞれの値は適所プロジェクトに応じて書き換えてください。Algolia・Firebase の設定値はそれぞれのダッシュボードから確認することができます。
-
indexing.jsにデータをインポートするためのコードをゴリゴリ書いていきます。
下記が最終的なコードになります。
const algoliasearch = require('algoliasearch')
const dotenv = require('dotenv')
const firebase = require('firebase')
const program = require('commander')
program.parse(process.argv)
dotenv.config()
firebase.initializeApp({
projectId: process.env.FIREBASE_PROJECT_ID
})
const collectionPath = program.args[0]
const indexName = program.args[1]
const db = firebase.firestore()
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
)
const index = algolia.initIndex(indexName)
const records = []
db.collection(collectionPath).get()
.then((snapshot) => {
snapshot.forEach((doc) => {
const childKey = doc.id
const childData = doc.data()
childData.objectID = childKey
records.push(childData)
console.log(doc.id, '=>', doc.data())
})
index.saveObjects(records).then(() => {
console.log('Documents imported into Algolia')
process.exit(0)
})
.catch(error => {
console.error('Error when importing documents into Algolia', error)
process.exit(1)
})
})
.catch((err) => {
console.error('Error getting documents', error)
})
次に前半と後半のブロックに分けてコードの解説をしていきます。
まず前半のブロックでは、それぞれのモジュールを初期化しています。program.parse(process.argv) では、コマンドラインで渡された引数をパースして、Firebase の collectionPath と Algolia に送信する indexName の値をそれぞれ定義しています。dotenv.config() では、.env ファイルから 3. で設定した環境変数を読み込み、取得した値を元に Firebase と Algolia の初期化を行います。
const algoliasearch = require('algoliasearch')
const dotenv = require('dotenv')
const firebase = require('firebase')
const program = require('commander')
program.parse(process.argv)
dotenv.config()
firebase.initializeApp({
projectId: process.env.FIREBASE_PROJECT_ID
})
const collectionPath = program.args[0]
const indexName = program.args[1]
const db = firebase.firestore()
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
)
const index = algolia.initIndex(indexName)
後半のブロックでは、collectionPath で指定した Firestore 内のコレクションデータを取得し、その取得した情報を元に index.saveObjects(records) で Algolia にデータを送信しています。ポイントは、取得したドキュメントデータに objectID を付与することです。Algolia に設定する全てのレコードには objectID という一意の値をつける必要があります。今回は、documentID を objectID として設定しましたが、objectID の設定を Algolia 側に任せることも可能です。詳しくは下記を参照してください。
※ 該当するインデックスは事前に Algolia で作成しておく必要があります。
※ 使用しているのは admin-sdk ではないため、コレクションの読み取りは Firestore に設定しているセキュリティルールに影響を受けることに注意してください。
const records = []
db.collection(collectionPath).get()
.then((snapshot) => {
snapshot.forEach((doc) => {
const childKey = doc.id
const childData = doc.data()
childData.objectID = childKey
records.push(childData)
console.log(doc.id, '=>', doc.data())
})
index.saveObjects(records).then(() => {
console.log('Documents imported into Algolia')
process.exit(0)
})
.catch(error => {
console.error('Error when importing documents into Algolia', error)
process.exit(1)
})
})
.catch((err) => {
console.error('Error getting documents', error)
})
5.最後に作成した indexing.js を使用して、collectionPath と indexName と共にコードを実行します。category/0/fields が Firestore の collectionPath で dev_field が Algolia の indexName になります。
$ node indexing.js category/0/fields dev_field
FKR3VvSikpJS7aca5VYO => {
imagePath: 'category/0/fields/FKR3VvSikpJS7aca5VYO.jpg',
map: GeoPoint { _lat: 90, _long: 149 },
name: '琵琶湖',
updatedAt: Timestamp { seconds: 1603897200, nanoseconds: 0 },
description: '滋賀県にあるバス釣りのメジャースポット',
prefectureName: '滋賀県',
createdAt: Timestamp { seconds: 1603897200, nanoseconds: 0 }
}
qcEJ2TR2rY0sDKtKl3zH => {
map: GeoPoint { _lat: 49, _long: 53 },
prefectureName: '茨城県',
description: '茨城県にあるバス釣りのメジャースポット',
createdAt: Timestamp { seconds: 1603897200, nanoseconds: 0 },
updatedAt: Timestamp { seconds: 1603897200, nanoseconds: 0 },
imagePath: 'category/0/fields/qcEJ2TR2rY0sDKtKl3zH.jpg',
name: '霞ヶ浦'
}
Documents imported into Algolia
という感じで Firestore から Algolia にデータを一括でインポートできるようになりました🎉
あとがき
なんとか書ききることができましたが、クライアントからの検索や Firestore からのトリガー処理について触れられなかったので、また別の記事で書こうと思います✍️
明日は @kawazu255 さんの記事です!