Configuration et connexion de l'API à l'instance MeiliSearch
Sommaire
Content de vous revoir. Précédemment, nous avons configuré notre VM, installé et configuré MeiliSearch.
Dans cette partie, nous allons créer une fonction cloud événementielle qui sera déclenchée lorsque les opérations seront effectuées dans nos données Cloud Firestore.
Nous voulons 3 choses :
Cloud Firestore a la capacité de déclencher un événement lorsqu'une opération est effectuée dans la base de données
Attention, cette fonctionnalité est encore en bêta.
Les événements pris en charge sont les événements de création, de mise à jour, de suppression et d'écriture. L'événement d'écriture se déclenche toutes les modifications d'un document.
Voici un exemple des données envoyées par les déclencheurs Firestore à la fonction Cloud
{
"oldValue": {
"createTime": "2021-05-11T04:18:53.600783Z",
"fields": {
"description": {
"stringValue": "Sed voluptates eos. "
},
"title": {
"stringValue": "Rerum cupiditate in vel. "
}
},
"name": "projects/bluebook-search/databases/(default)/documents/posts/ksP4335m2bhSzNYZ5YEG",
"updateTime": "2021-05-11T04:21:16.351247Z"
},
"updateMask": { "fieldPaths": ["title", "description"] },
"value": {
"createTime": "2021-05-11T04:18:53.600783Z",
"fields": {
"description": {
"stringValue": "Vero reiciendis ."
},
"title": {
"stringValue": "Et corrupti et rerum ex asperiores enim consequatur. Nihil et fugiat optio aliquid. Dolore."
}
},
"name": "projects/bluebook-search/databases/(default)/documents/posts/ksP4335m2bhSzNYZ5YEG",
"updateTime": "2021-05-11T04:21:16.351247Z"
}
}
Plus d'exemple dans ce commit
La description
{
"oldValue": { // Update and Delete operations only
// Un objet Document contenant un instantané de document de pré-opération
},
"updateMask": { // Update operations only
// Un objet DocumentMask qui répertorie les champs modifiés.
},
"value": {
// Un objet Document contenant un instantané de document post-opération
}
}
Puisque nous devons réagir à 3 événements de Firestore (créer, mettre à jour et supprimer), nous devons écrire et déployer 3 nouvelles fonctions Cloud. L'utilisation de l'événement d'écriture peut être une alternative, mais elle introduira une nouvelle logique.
Tout d'abord, créons le Serverless VPC Access
gcloud compute networks vpc-access connectors create posts-handlers-connector \
--network default \
--region europe-west1 \
--range 10.8.0.0/28
Nous pouvons vérifier la création de notre connecteur avec la commande
gcloud compute networks vpc-access connectors describe posts-handlers-connector \
--region europe-west1
Lorsqu'un article est ajouté, nous voulons le prendre et l'envoyer à MeiliSearch pour qu'il soit enregistré. Nous utiliserons le SDK JavaScript MeiliSearch. Il faut d'abord l'installer
npm install --save meilisearch
Ensuite nous l'initialiserons avec nos configurations
const { MeiliSearch } = require('meilisearch')
const client = new MeiliSearch({
host: 'http://127.0.0.1:7700',
apiKey: 'masterKey',
})
Dans notre configuration, notre host est l'adresse IP privée de notre VM puisque nous nous y connectons via le VPC Serverless Access et l'apiKey est la clé Master que nous avons enregistrée dans Secret Manager (partie 3). Pour accéder à notre secret, nous aurons besoin de NodeJS Librairie for Secret Manager.
npm install --save @google-cloud/secret-manager
Dans le secrets.service.js
nous aurons un code comme celui-ci
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();
async function getSecretVersion(secretName) {
const [version] = await client.accessSecretVersion({ name: secretName, });
const payload = version.payload.data.toString();
return payload
}
module.exports = {
getSecretVersion,
}
N'oubliez pas d'ajouter l'autorisation
secretmanager.versions.access
au compte de service AppEngine et que le secretName doit suivre le formatprojects/my-project/secrets/my-secret/versions/latest
La fonction pour gérer l'événement post-ajouté ressemble à ceci
async function onPostAdded(event) {
const client = await searchService.getMeilisearchClient();
const index = config.meilisearchPostsIndex
// Get the added document
const addedDocument = event.value;
// Get the id, title and description
const postId = addedDocument.name.split('/posts/')[1]
const postTitle = addedDocument.fields.title.stringValue;
const postDescription = addedDocument.fields.description.stringValue;
// Add the document to the posts index in meilisearch
return await client.index(index).addDocuments(
[
{
id: postId,
title: postTitle,
description: postDescription
}
]
)
}
Pour le déployer, nous allons utiliser cette très longue commande
gcloud functions deploy postAddedHandler \
--entry-point=postAddedHandler \
--runtime nodejs14 \
--set-env-vars MEILISEARCH_ADDRESS=http://10.132.0.2 \
--trigger-event "providers/cloud.firestore/eventTypes/document.create" \
--trigger-resource "projects/bluebook-search/databases/(default)/documents/posts/{id}" \
--region=europe-west1 \
--vpc-connector posts-handlers-connector
async function onPostUpdated(event) {
const client = await searchService.getMeilisearchClient();
const index = config.meilisearchPostsIndex
// Get the added document
const updatedDocument = event.oldValue;
if (updatedDocument) {
// Get the id, title and description
const postId = updatedDocument.name.split('/posts/')[1]
const postTitle = updatedDocument.fields.title.stringValue;
const postDescription = updatedDocument.fields.description.stringValue;
try {
const response = await client.index(index).updateDocuments(
[
{
id: postId,
title: postTitle,
description: postDescription
}
]
)
console.log(`updating document to ${index} with operation : ${response.updateId}`)
} catch (error) {
console.error(`updating document to ${index} failed`);
throw error;
}
}
}
Déploiement
gcloud functions deploy postUpdatedHandler \
--entry-point=postUpdatedHandler \
--runtime nodejs14 \
--set-env-vars MEILISEARCH_ADDRESS=http://10.132.0.2 \
--trigger-event "providers/cloud.firestore/eventTypes/document.update" \
--trigger-resource "projects/bluebook-search/databases/(default)/documents/posts/{id}" \
--region=europe-west1 \
--vpc-connector posts-handlers-connector
Pour le code complet, jetez un oeil à ce commit
Nous pouvons réduire les fonctions cloud du gestionnaire d'événements en utilisant l'événement d'écriture write
de Cloud Firestore couplé à une certaine logique.
Puisque nous avons déjà la logique 3 pour la gestion de l'opération de création create
, de mise à jour update
et de suppression delete
, nous n'avons qu'à lancer la bonne fonction en fonction de l'événement.
La nouvelle logique ressemblera à ceci :
La différence entre les valeurs de chaque événement peut nous aider à savoir de quel événement il s'agit
value
dans l'événement n'est pas vide et que l'objet oldValue
n'est pas vide non plus, il s'agit d'un événement de mise à jour. Nous pouvons également vérifier l'objet updateMask
. S'il n'est pas vide, il s'agit d'un événement de mise à jour.value
dans l'événement n'est pas vide et que oldValue
est vide, il s'agit d'un événement de création.oldValue
dans l'événement n'est pas vide et que la valeur est vide, il s'agit d'un événement de suppression.code
const { onPostAdded } = require('./post-added.handler')
const { onPostDeleted } = require('./post-deleted.handler')
const { onPostUpdated } = require('./post-updated.handler')
const isEmpty = require('lodash/isEmpty')
async function onPostWritten(event) {
const value = event.value;
const oldValue = event.oldValue;
const updateMask = event.updateMask;
if (!isEmpty(updateMask)) {
// It is an update event
await onPostUpdated(event)
} else if (isEmpty(value) && !isEmpty(oldValue)) {
// It is an delete event
await onPostDeleted(event)
} else if (!isEmpty(value) && isEmpty(oldValue)) {
// It is an create event
await onPostAdded(event)
} else {
console.error('the event does not match any condition')
}
}
require('dotenv').config()
const { onPostWritten } = require('./handlers')
exports.postWrittenHandler = onPostWritten
Déploiement
gcloud functions deploy postWrittenHandler \
--entry-point=postWrittenHandler \
--runtime nodejs14 \
--set-env-vars MEILISEARCH_ADDRESS=http://10.132.0.2 \
--trigger-event "providers/cloud.firestore/eventTypes/document.write" \
--trigger-resource "projects/bluebook-search/databases/(default)/documents/posts/{id}" \
--region=europe-west1 \
--vpc-connector posts-handlers-connector
La dernière étape de cet article consiste à ajouter l'endpoint de recherche à l'API.
Pour simplifier les choses, nous ajouterons la recherche au point de terminaison /posts
déjà existant.
Si un paramètre de requête q
est ajouté avec une valeur alors nous ferons la recherche avec notre moteur de recherche.
const searchPosts = async (key) => {
const client = await searchService.getMeilisearchClient();
const index = config.meilisearchPostsIndex;
return client.index(index).search(key);
};
const getPosts = catchAsync(async (req, res) => {
const key = req.query.q
let posts = [];
if (key) {
const result = await postsService.searchPosts(key);
posts = result.hits;
} else {
posts = await postsService.getPosts();
}
return res.status(200).send(posts);
});
Puisque notre API se connecte maintenant à notre VPC d'accès sans serveur, nous devons mettre à jour la commande de déploiement.
gcloud functions deploy postsApi \
--entry-point=postsApi \
--set-env-vars MEILISEARCH_ADDRESS=http://10.132.0.2 \
--runtime nodejs14 \
--trigger-http \
--region=europe-west1 \
--vpc-connector posts-handlers-connector \
--allow-unauthenticated
.
Félicitation 👌🏾, tout fonctionne bien.
Dans la prochaine et dernière, nous explorerons les petits problèmes de notre approche.