Que vous créiez une application à partir de zéro avec zéro utilisateur ou que vous ajoutiez des fonctionnalités à une application existante, travailler avec des données pendant le développement est une nécessité. Cela peut prendre différentes formes, des API de données factices lisant les fichiers de données en cours de développement, aux déploiements de bases de données prédéfinies reflétant étroitement un environnement de production attendu.
Je préfère ce dernier car je trouve que moins d’écarts par rapport à mon ensemble d’outils de production entraînent moins de bogues.
Un humble début
Pour les besoins de cette discussion, supposons que nous construisons une plate-forme d’apprentissage en ligne proposant divers cours de codage. Dans sa forme la plus simple, notre couche API Node.js pourrait ressembler à ceci.
// server.js
const express = require("express");
const App = express();
const courses = [
{title: "CSS Fundamentals", "thumbnail": "https://fake-url.com/css"}],
{title: "JavaScript Basics", "thumbnail": "https://fake-url.com/js-basics"}],
{title: "Intermediate JavaScript", "thumbnail": "https://fake-url.com/intermediate-js"}
];
App.get("/courses", (req, res) => {
res.json({data: courses});
});
App.listen(3000);
Si vous n’avez besoin que de quelques éléments pour commencer à créer votre interface utilisateur, cela suffit pour commencer. Faire un appel à notre /courses
endpoint renverra tous les cours définis dans ce fichier. Cependant, que se passe-t-il si nous voulons commencer les tests avec un ensemble de données plus représentatif d’une application complète basée sur une base de données ?
Travailler avec JSON
Supposons que nous héritions d’un script exportant un tableau JSON contenant des milliers de cours. Nous pourrions importer les données, comme ça.
// courses.js
module.exports = [
{title: "CSS Fundamentals", "thumbnail": "https://fake-url.com/css"}],
{title: "JavaScript Basics", "thumbnail": "https://fake-url.com/js-basics"}],
{title: "Intermediate JavaScript", "thumbnail": "https://fake-url.com/intermediate-js"},
...
];
// server.js
...
const courses = require("/path/to/courses.js");
...
Cela élimine le besoin de définir nos données fictives dans notre fichier serveur, et nous avons maintenant beaucoup de données avec lesquelles travailler. Nous pourrions améliorer notre point de terminaison en ajoutant des paramètres pour paginer les résultats et définir des limites sur le nombre d’enregistrements renvoyés. Mais qu’en est-il de permettre aux utilisateurs de publier leurs propres cours ? Et si vous éditiez des cours ?
Cette solution devient rapidement incontrôlable lorsque vous commencez à ajouter des fonctionnalités. Nous devrons écrire du code supplémentaire pour simuler les fonctionnalités d’une base de données relationnelle. Après tout, les bases de données ont été créées pour stocker des données. Alors, faisons cela.
Chargement en masse de JSON avec Sequelize
Pour une application de cette nature, PostgreSQL est une sélection de base de données appropriée. Nous avons la possibilité d’exécuter PostgreSQL localement ou de nous connecter à une base de données cloud native compatible avec PostgreSQL, comme YugabyteDB Managed. En plus d’être une base de données SQL distribuée hautement performante, les développeurs utilisant YugabyteDB bénéficient d’un cluster qui peut être partagé par plusieurs utilisateurs. Au fur et à mesure que l’application se développe, notre couche de données peut évoluer vers plusieurs nœuds et régions.
Après avoir créé un compte YugabyteDB Managed et mis en place un cluster de base de données gratuit, nous sommes prêts à amorcer notre base de données et à refactoriser notre code à l’aide de Sequelize. L’ORM Sequelize nous permet de modéliser nos données pour créer des tables de base de données et exécuter des commandes. Voici comment cela fonctionne.
Tout d’abord, nous installons Sequelize depuis notre terminal.
// terminal
> npm i sequelize
Ensuite, nous utilisons Sequelize pour établir une connexion à notre base de données, créer une table et ensemencer notre table avec des données.
// database.js
// JSON-array of courses
const courses = require("/path/to/courses.js");
// Certificate file downloaded from YugabyteDB Managed
const cert = fs.readFileSync(CERTIFICATE_PATH).toString();
// Create a Sequelize instance with our database connection details
const Sequelize = require("sequelize");
const sequelize = new Sequelize("yugabyte", "admin", DB_PASSWORD, {
host: DB_HOST,
port: "5433",
dialect: "postgres",
dialectOptions: {
ssl: {
require: true,
rejectUnauthorized: true,
ca: cert,
},
},
pool: {
max: 5,
min: 1,
acquire: 30000,
idle: 10000,
}
});
// Defining our Course model
export const Course = sequelize.define(
"course",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
title: {
type: DataTypes.STRING,
},
thumbnail: {
type: DataTypes.STRING,
},
}
);
async function seedDatabase() {
try {
// Verify that database connection is valid
await sequelize.authenticate();
// Create database tables based on the models we've defined
// Drops existing tables if there are any
await sequelize.sync({ force: true });
// Creates course records in bulk from our JSON-array
await Course.bulkCreate(courses);
console.log("Courses created successfully!");
} catch(e) {
console.log(`Error in seeding database with courses: ${e}`);
}
}
// Running our seeding function
seedDatabase();
En tirant parti de la méthode bulkCreate de Sequelize, nous sommes en mesure d’insérer plusieurs enregistrements dans une seule instruction. C’est plus performant que d’insérer des requêtes une par une, comme celle-ci.
. . .
// JSON-array of courses
const courses = require("/path/to/courses.js");
async function insertCourses(){
for(let i = 0; i < courses.length; i++) {
await Course.create(courses[i]);
}
}
insertCourses();
Les insertions individuelles entraînent une surcharge de connexion, d’envoi de requêtes, d’analyse des requêtes, d’indexation, de fermeture de connexions, etc. sur une base ponctuelle. Bien sûr, certaines de ces préoccupations sont atténuées par la mise en commun des connexions, mais d’une manière générale, les avantages en termes de performances de l’insertion en bloc sont immenses, sans parler de beaucoup plus pratique. La méthode bulkCreate est même livrée avec une option d’analyse comparative pour transmettre les temps d’exécution des requêtes à vos fonctions de journalisation, si les performances sont la principale préoccupation.
Maintenant que notre base de données est ensemencée avec des enregistrements, notre couche API peut utiliser ce modèle Sequelize pour interroger la base de données et renvoyer des cours.
// server.js
const express = require("express");
const App = express();
// Course model exported from database.js
const { Course } = require("/path/to/database.js")
App.get("/courses", async (req, res) => {
try {
const courses = await Course.findAll();
res.json({data: courses});
} catch(e) {
console.log(`Error in courses endpoint: ${e}`);
}
});
App.listen(3000);
Eh bien, c’était facile ! Nous sommes passés d’une structure de données statique à une base de données entièrement fonctionnelle en un rien de temps.
Que se passe-t-il si l’ensemble de données nous est fourni dans un autre format de données, par exemple un fichier CSV exporté depuis Microsoft Excel ? Comment pouvons-nous l’utiliser pour ensemencer notre base de données ?
Travailler avec des CSV
Il existe de nombreux packages NPM pour convertir des fichiers CSV en JSON, mais aucun n’est aussi facile à utiliser que csvtojson. Commencez par installer le package.
// terminal
> npm i csvtojson
Ensuite, nous utilisons ce package pour convertir notre fichier CSV en un tableau JSON, qui peut être utilisé par Sequelize.
// courses.csv
title,thumbnail
CSS Fundamentals,https://fake-url.com/css
JavaScript Basics,https://fake-url.com/js-basics
Intermediate JavaScript,https://fake-url.com/intermediate-js
// database.js
...
const csv = require('csvtojson');
const csvFilePath = "/path/to/courses.csv";
// JSON-array of courses from CSV
const courses = await csv().fromFile(csvFilePath);
...
await Course.bulkCreate(courses);
...
Tout comme avec notre bien formaté courses.js
fichier, nous sommes en mesure de convertir facilement notre courses.csv
fichier pour insérer des enregistrements en bloc via Sequelize.
Conclusion
Développer des applications avec des données codées en dur ne peut nous mener que très loin. Je trouve qu’investir dans l’outillage au début du processus de développement me met sur la voie d’un codage sans bogue (du moins je l’espère !)
En chargeant en bloc des enregistrements, nous sommes en mesure de travailler avec un ensemble de données représentatif, dans un environnement d’application représentatif. Comme je suis sûr que beaucoup en conviennent, c’est souvent un goulot d’étranglement majeur dans le processus de développement d’applications.