[Firebase, Algolia] Cloud Extension ์ผ๋ก Algolia ์ฐ๊ฒฐํด์ ๊ฒ์ ๊ตฌํ
Firebase ๋ text ๊ฒ์์ด exact search ๊ฒ์๋ง ๊ฐ๋ฅํฉ๋๋ค.
https://firebase.google.com/docs/firestore/solutions/search
๋ฐ๋ผ์ ๊ฒ์ ์ ์ฒด์ธ Algolia ๋ฅผ ์ฐ๋ํ์ฌ ์ฌ์ฉํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ ๋งํฌ์์๋ ๋ค๋ฅธ ์ ์ฒด๋ค๋ ์๊ฐํ์์ผ๋ Algolia ๊ฐ ์ฒ์ ์ฌ์ฉํ๊ธฐ์ ํธํ๋ค๊ณ ํ์ฌ ์ ํํ์ต๋๋ค.
firebase ์์ Algolia ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ค์์ ๊ณผ์ ์ ๋ฐ๋ผ๊ฐ๋ฉด ๋ฉ๋๋ค.
Firebase Extension ์ ์ฌ์ฉํ๋ฉด ๊ฐํธํ๊ฒ ๋ ๊ฑฐ๋ผ๊ณ ์๊ฐํ๋๋ฐ, Firebase Extension ์ด beta ๊ธฐ๋ ํ๊ณ , ์ง์ Functions ๋ก ์์ ํ๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ์์ ๊ทธ๋ ๊ฒ ์งํํด๋ณด๊ฒ ์ต๋๋ค.
1) Firebase Functions ์ค๋น
Firebase Functions ๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ Firebase Functions ๋ฅผ ์ค๋นํด์ผ ํฉ๋๋ค.
์๋ ํฌ์คํ ์ผ๋ก ์ด ๋ด์ฉ์ ์๋ตํ๊ฒ ์ต๋๋ค.
[Firebase] Firebase Functions CLI ์ฌ์ฉํ๊ธฐ, ํจ์ ๋ฐฐํฌ, ๋ฆฌ์ ๋ณ๊ฒฝ
Firebase ๋ Google ์ ๋ชจ๋ฐ์ผ ๋ฐ ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ํ๋ ํผ์ ๋๋ค. ์ค์๊ฐ ๋ฐ์ดํฐ ๋๊ธฐํ ๋ฑ์ ์ฅ์ ์ด ์์ด ๋ฐฑ์๋๋ฅผ ๊ตฌํํ ์ฌ๊ฑด์ด ๋์ง ์๊ฑฐ๋ ๋น์ฉ์ ์ธ ์ธก๋ฉด์ ๊ณ ๋ คํ๋ค๋ฉด ์ถฉ๋ถํ ๋งค๋ ฅ์ ์ธ
iforint.tistory.com
๋ํ, ์ด ์ํฐํด์์ ์ ๋ฐ์ ์ธ ๊ณผ์ ์ ์ดํดํ ํ ํ๋ก์ ํธ์ ํ์ํ ๋ด์ฉ์ ์์ฑํ์์ต๋๋ค. (์ค๋ฅ๊ฐ ๋๋ ๋ถ๋ถ ๋ฑ)
์ค๋ช ์ด ์ ๋์ด ์์ผ๋ ์ญ ์ฝ์ด๋ณด๋ฉด ์ข์ต๋๋ค.
https://rsfarias.medium.com/how-to-set-up-firestore-and-algolia-319fcf2c0d37
How to set up Firestore and Algolia using Cloud Functions.
An unofficial step-by-step guide on how to set up and perform full-text search using Firestore and Algolia.
rsfarias.medium.com
2) algoliasearch ์ค์น
functions ํด๋๋ก ์ด๋ํ์ฌ npm ์ผ๋ก algoliasearch ๋ฅผ ์ค์นํด์ค๋๋ค.
$ cd functions
$ npm install algoliasearch --save
3) Algolia Keys ๋ฑ๋ก
Algolia ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด Algolia ์ ๊ฐ์ ํ ํ๋ก์ ํธ๋ฅผ ์์ฑํด์ API Keys ๋ฅผ ์ป์ด์ผ ํฉ๋๋ค.
ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฉด ์๋์ ๊ฐ์ด API Keys ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
(์ฐธ๊ณ : dev ๋ฅผ ์ํ ๊ฒ๊ณผ ํ๋ก๋์ ์ ์ํ ๊ฒ์ ๋ถ๋ฆฌํด์ ํ๊ฒฝ์ ์กฐ์ฑํ๋ฉด ์ข์ต๋๋ค.)
Admin API Key ๋ ์ ๋ ๋ ธ์ถํ๋ฉด ์๋๋ ์ ๋ณด์ ๋๋ค. (front-end ๋ฑ ๋ ธ์ถ๋ ์ ์๋ ๊ณต๊ฐ์๋ ์ ๋ ๋๋ฉด ์๋ฉ๋๋ค)
Firebase Cloud Functions ๋ ์ด๋ฐ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ํ๊ฒฝ ๋ณ์ (https://firebase.google.com/docs/functions/config-env) ๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฐ๋ผ์, ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
$ firebase functions:config:set algolia.appid="YOUR_APP_ID" algolia.apikey="YOUR_API_KEY"
- appid: Algolia ์ฑ id
- apikey: Admin API Key
๋ค์๊ณผ ๊ฐ์ด config ๊ฐ ์ ๋ฐ์ดํธ ๋์๋ค๊ณ ๋์ค๋ฉด ์๋ฃ๋ ๊ฒ์ ๋๋ค.
ํ์ธํ๊ณ ์ถ์ผ๋ฉด
$ firebase functions:config:get
์ผ๋ก ๋ณผ ์ ์์ต๋๋ค.
4) Algolia ์ index ๋ง๋ค๊ธฐ
์ด์ firestore ์ ์ด๋ค collection ์ Algolia ์ index ํ ๊ฒ์ธ์ง๋ฅผ ์ ํ๋ฉด ๋ฉ๋๋ค.
๊ทธ ์ ์ Algolia ์ ์ฐ๋ํ๋ ๊ณผ์ ์ ์งง๊ฒ ์ง์ด๋ณด๋ฉด,
- 1) Algolia ์ ํ๋ก์ ํธ ์์ฑ
- 2) Algolia ์ index ์์ฑ
- 3) ๋ฐ์ดํฐ๋ฅผ Algolia ์ ์ฌ๋ฆฌ๊ธฐ (file / API / manually)
- 4) ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ ์ Algolia ์ ๋ฐ์๋๋๋ก functions ์์ฑ
- 5) Search ์์ฑ
์ด ๋ฉ๋๋ค. ์ด๋ Algolia ํ๋ก์ ํธ ์ด๊ธฐ ์์ฑ ์์๋ ๋ค์๊ณผ ๊ฐ์ด ํ์ธํ ์ ์์ต๋๋ค.
Algolia ํ๋ก์ ํธ์ ํ์ํ index ๋ฅผ ๋ง๋ค์ด์ฃผ๋ฉด ๋ฉ๋๋ค. ์ด๋ฆ์ ํธํ๋๋ก ์ง์ผ๋ฉด ๋ฉ๋๋ค.
5) Firestore ๋ด์ฉ์ Algolia ์ ์ฌ๋ฆฌ๊ธฐ
์ด ๊ณผ์ ์ ๊ธฐ์กด์ ๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒฝ์ฐ ์งํํด์ฃผ๋ฉด ๋ฉ๋๋ค.
์ฆ, ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ Algolia ์ ์ฌ๋ ค์ ๊ฒ์์ด ๋๋๋ก indexing ํ๋ ๊ณผ์ ์ ๋๋ค.
๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฆฌ๋ ๋ฐ์๋ json file ๋ก ์ฌ๋ฆฌ๊ฑฐ๋ API ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋, ํ๋ํ๋ ์ง์ ์ฌ๋ฆฌ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
API ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด script ๋ฅผ ์ง์ ์์ฑํ ์ ์์ต๋๋ค.
์์ ๋งํ๋ฏ, ์ด ๊ณผ์ ์ ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฆฌ๋ ๊ณผ์ ์ด๋ฏ๋ก ๋จ ํ ๋ฒ๋ง ์คํ๋์ด์ผ ํฉ๋๋ค!
ํ์ผ์ ํ๋ ๋ง๋ค์ด์ ์คํ์์ผ๋ ๋์ง๋ง, cloud functions ๋ก ํด๋น ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ต๋๋ค.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
// Set up Algolia
const {default: algoliasearch} = require("algoliasearch");
const algoliaClient = algoliasearch(
functions.config().algolia.appid_dev,
functions.config().algolia.apikey_dev,
);
const indexName = "dev_title";
const collectionIndex = algoliaClient.initIndex(indexName);
// Create a HTTP request cloud functions
exports.sendCollectionToAlgolia = functions
.region("asia-northeast2")
.https.onRequest(async (request, response) => {
const firestore = admin.firestore();
const algoliaRecords = [];
const snapshot = await firestore.collection("Record").listDocuments.get();
snapshot.forEach((doc) => {
const document = doc.data();
const record = {
objectID: doc.id,
title: document.title,
};
algoliaRecords.push(record);
});
// After all records are created, save them to Algolia
collectionIndex.saveObjects(algoliaRecords, (_error, content) => {
response.status(200)
.send("COLLECTION was indexed to Algolia successfully.");
});
});
์์ ์ ํ๋ก์ ํธ์ ๋ง๊ฒ ๋ด๋ถ ์ฝ๋๋ ๋ณํํด์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
6) Firestore ์ ๋ฐ์ดํฐ๊ฐ ์ ๋ฐ์ดํธ๋ ๋ Algolia ์ ๋ฐ์๋๋๋ก functions ์์ฑ
Firebase ์ ๋ฐ์ดํฐ๊ฐ ์์ฑ/์์ /์ญ์ ๋ ๋, Algolia ๋ ํด๋น ๋ด์ฉ์ ๊ฐ์ง๊ณ "์ต์ "์ผ๋ก ์ ๋ฐ์ดํธํด์ผ ํฉ๋๋ค.
Firestore ์์ ํน์ ๊ฒฝ๋ก์ ๋ฐ์ดํฐ ์์ฑ/์์ /์ญ์ ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ์ ํธ๋ฆฌ๊ฑฐ๋๋ ํจ์๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
๊ทธ ์ ์ ๋จผ์ Algolia client ๋ index ๋ฅผ ์ ์ํด์ผ ํฉ๋๋ค. ์ด๋ 5) ์ ํฌํจ๋ ๊ณผ์ ์ด์ง๋ง, ํน์ ํด๋น ๊ณผ์ ์ด ํ์์์ด์ ๊ฑด๋๋ฐ์๋ค๋ฉด ์ฒดํฌํ๊ณ ๋์ด๊ฐ์ผ ํฉ๋๋ค.
// Set up Algolia
const {default: algoliasearch} = require("algoliasearch");
const algoliaClient = algoliasearch(
functions.config().algolia.appid_dev,
functions.config().algolia.apikey_dev,
);
const indexName = "dev_title";
const collectionIndex = algoliaClient.initIndex(indexName);
์ด์ฒ๋ผ appId ์ APIKey ๋ก client ๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ , index ๋ฅผ ์ง์ ํด์ค๋๋ค.
6-1) onCreate
์๋ก์ด document ๊ฐ ์๊ธธ ๊ฒฝ์ฐ, Algolia ์ ๋ฐ์ํฉ๋๋ค.
// define functions:collectionOnCreate
exports.collectionOnCreate = functions
.region("asia-northeast2")
.firestore.document("Record/{recordId}")
.onCreate(async (snapshot, context) => {
await saveDocumentInAlgolia(snapshot);
});
const saveDocumentInAlgolia = async (snapshot) => {
if (snapshot.exists) {
const data = snapshot.data();
console.log(snapshot.id);
if (data) {
const record = {
objectID: snapshot.id,
title: data.title,
};
collectionIndex.saveObject(record)
.catch((res) => console.log("Error with: ", res));
}
}
};
- Record Collections ์ด ์๋ค๊ณ ๊ฐ์ ํ๋ฉด, ๊ทธ id ๋ ์์ ๊ฐ์ด {rocordId} ๋ก ์์ฑํด์ฃผ๋ฉด ๋ฉ๋๋ค.
- indexing ํ ๋ด์ฉ๋ง record ์ ๋ด์ collectionIndex ์ saveObject ๋ก ์ ์ฅํด์ฃผ๋ฉด ๋ฉ๋๋ค.
์ด๋, Algolia ์์ ์ทจ๊ธํ๋ unique ํ id ์ธ "objectID" ๋ฅผ ๊ผญ ์์๋ก ๋ฃ์ด์ฃผ๊ฑฐ๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํด์ฃผ์ด์ผ ํฉ๋๋ค.
index.saveObject(object object, {
autoGenerateObjectIDIfNotExist: true
})
6-2) onUpdate
Algolia ์ ์๋ ์๋ ์์๋ฅผ ์ ๋ฐ์ดํธํ๋ ํ๋ ๋ฐฉ๋ฒ์๋ 2๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
- saveObject(record): record ์ ์๋ ์ ๋ณด๋ก ๊ธฐ์กด ์ ๋ณด๋ฅผ ๋ฎ์ด์๋๋ค.
- partialUpdateObjects(record): record ์ ๋ฃ์ด์ค ์ ๋ณด๋ง ์๋ก ์ ์ฅํฉ๋๋ค.
ํน์ ์์๋ง ์ ๋ฐ์ดํธํ๊ณ ์ ํ๋ค๋ฉด partialUpdateObjects ๋ฅผ, ๋ชจ๋ ์ ๋ณด๋ฅผ ์ฌ๊ฐฑ์ ํ๊ณ ์ ํ๋ค๋ฉด saveObject ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
์ฌ๊ธฐ์๋ ๊ธฐ์กด์ ์ ๋ณด๋ฅผ ์ฌ์ฉํด์ ํ์ํ ์ ๋ณด๋ง ์ ๋ฐ์ดํธํ ๊ฒ์ด๋ฏ๋ก, partialUpdateObjects ๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค.
// define functions:ticcleOnUpdate
exports.ticcleOnUpdate = functions
.region("asia-northeast2")
.firestore.document("Record/{recordId}")
.onUpdate(async (change, context) => {
await updateDocumentInAlgolia(context.params.recordId, change);
});
const updateDocumentInAlgolia = async (objectID, change) => {
const before = change.before.data();
const after = change.after.data();
if (before && after) {
const record = {objectID: objectID};
let flag = false;
if (before.title != after.title) {
record.title = after.title;
flag = true;
}
if (before.content != after.content) {
record.content = after.content;
flag = true;
}
if (flag) {
// update
collectionIndex.partialUpdateObject(record)
.catch((res) => console.log("Error with: ", res));
}
}
};
- .firestore.document("Record/{recordId}"): ์ฌ๊ธฐ ๊ฒฝ๋ก์ ์๋ {recordId} ๋
- .onUpdate(async (change, context): context ์ ํฌํจ๋์ด ์ ๋ฌ๋ฉ๋๋ค. ๊ทธ๋์ context.params.recordId ๋ก ์ ๊ทผํ ์ ์์ต๋๋ค.
- title ์ content ๋ฅผ indexing ํ๋ค๊ณ ๊ฐ์ ํ์ ๋ before data ์ after data ๋ฅผ ๋น๊ตํด์ ํ์ํ ๋ด์ฉ๋ง partialUpdateObjects ์ ์ฌ์ฉํ์ฌ ์ ๋ฐ์ดํธํด์ค๋๋ค.
6-3) onDelete
์ญ์ ๋ ๊ฐ๋จํฉ๋๋ค. ํด๋นํ๋ objectID ๋ฅผ ์ญ์ ํด์ฃผ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
// define functions:ticcleOnDelete
exports.ticcleOnDelete = functions
.region("asia-northeast2")
.firestore.document("Record/{recordId}")
.onDelete(async (snapshot, context) => {
await deleteDocumentInAlgolia(snapshot);
});
const deleteDocumentInAlgolia = async (snapshot) => {
if (snapshot.exists) {
const objectID = snapshot.id;
collectionIndex.deleteObject(objectID)
.catch((res) => console.log("Error with: ", res));
}
};
์ฌ๊ธฐ๊น์ง ํ๊ณ ํ ์คํธ๋ฅผ ํด๋ณด๋ฉด ์ ์ํ ํจ์๋ค์ด ์ ๋์ํ๊ณ , Algolia ์๋ ์ ๋ฐ์๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค์์๋ React Native ์ Algolia ๋ฅผ ์ฐ๊ฒฐํ์ฌ ๊ฒ์์ ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.