まえがき
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 さんの記事です!