3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FirebaseAdvent Calendar 2020

Day 14

Firestore から Algolia へコレクションデータを一括インポート

Posted at

まえがき

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 ファイルを作成する感じで実装した手順を紹介します。

  1. まずは、Firebase CLI でプロジェクトを設定します。こちらについては記事の重要な部分ではないため割愛させていただきますが、Firebase の公式ドキュメントを参照すれば簡単にセットアップできます。

    https://firebase.google.com/docs/cli?hl=ja

  2. 次に functions ディレクトリに移動して、インポートに使用する modules をインストールします。npm 5.0.0 以降からは --save オプションを付けずとも save されるようになったようですね。

     $ cd functions
     $ npm install algoliasearch
     $ npm install dotenv
     $ npm install commander
     $ npm install firebase
    

僕をはじめとして、jsmodule にあまり馴染みのない方向けにそれぞれの役割を書いておきます。

|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|
  1. .env ファイルに必要な環境変数を設定します。FIREBASE_PROJECT_ID も今回は含めましたが、正直 ALGOLIA_APP_IDALGOLIA_API_KEY が直接ソースコードに書き込まれなければいい気もします🤔

     ALGOLIA_APP_ID=YOUR_ALGOLIA_APP_ID
     ALGOLIA_API_KEY=YOUR_ALGOLIA_API_KEY
     FIREBASE_PROJECT_ID=YOUR_FIREBASE_PROJECT_ID
    

それぞれの値は適所プロジェクトに応じて書き換えてください。AlgoliaFirebase の設定値はそれぞれのダッシュボードから確認することができます。

  1. indexing.js にデータをインポートするためのコードをゴリゴリ書いていきます。

下記が最終的なコードになります。

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) では、コマンドラインで渡された引数をパースして、FirebasecollectionPathAlgolia に送信する indexName の値をそれぞれ定義しています。dotenv.config() では、.env ファイルから 3. で設定した環境変数を読み込み、取得した値を元に FirebaseAlgolia の初期化を行います。

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)

後半のブロックでは、collectionPath で指定した Firestore 内のコレクションデータを取得し、その取得した情報を元に index.saveObjects(records)Algolia にデータを送信しています。ポイントは、取得したドキュメントデータに objectID を付与することです。Algolia に設定する全てのレコードには objectID という一意の値をつける必要があります。今回は、documentIDobjectID として設定しましたが、objectID の設定を Algolia 側に任せることも可能です。詳しくは下記を参照してください。

※ 該当するインデックスは事前に Algolia で作成しておく必要があります。
※ 使用しているのは admin-sdk ではないため、コレクションの読み取りは Firestore に設定しているセキュリティルールに影響を受けることに注意してください。

indexing.js
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 を使用して、collectionPathindexName と共にコードを実行します。category/0/fieldsFirestorecollectionPathdev_fieldAlgoliaindexName になります。

$ 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 さんの記事です!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?