Voy a poblar una base de datos de MongoDB con datos falos. Esto puede ser útil para hacer pruebas manuales o hacer mockups en tests.
En este caso simplemente voy a usar FakerJS, aunque está salpicada por la polémica. Más sobre esta historia al final.
Voy a utilizar node para este proyecto. Así que en el package.json voy a agregar lo siguiente.
"scripts": {
"start": "node src",
"seed:fresh": "node seeds" <--- Este es el nuevo
}
La estrutura de carpetas es similar a esta
node_modules/
seeds/
index.js
operarios.js
sotck.js
referencias.js
registros.js
src/
index.js
package.json
package-lock.json
...
Cada archivo pertenece a un seeder concreto y el siguiente aspecto
const {db} = require('./../src/db')
const {faker} = require('@faker-js/faker/locale/es')
const seedOperarios = async (cantidad,session=null) => {
await db.collection('operarios').deleteMany({},{session})
let operarios = []
for(let i=0;i<cantidad;i++){
const nombre = faker.person.fullName()
operarios.push({nombre})
}
// Removiendo duplicados
let operariosUnicos = [... new Set(operarios)]
await db.collection('operarios').insertMany(operariosUnicos,{session})}
module.exports = {
seedOperarios
}
Primero hay que conectar el cliente porque este script se ejecuta independientemente del proyecto. Por lo cual la conexión es nueva.
Luego se crea la sesión que se necesita para trabajar con transacciones en Mongo. Esto se explica en la siguiente sección.
Por ultimo se ejecutan los seeders en batch de forma concurrente con Promise.all() teniendo en cuenta cuales tienen dependencias entre sí. Se explica más abajo.
const { client } = require('./../src/db')
const { seedReferencias } = require('./referencias')
const { seedOperarios } = require('./operarios')
const { seedStock } = require('./stock')
const { seedRegistros } = require('./registros')
const runSeeds = async () => {
// Conexion con la DB
await client.connect()
const session = client.startSession()
try {
await session.withTransaction(async () => {
await Promise.all([seedReferencias(3_000, session), seedOperarios(100,session)])
// Dependientes de las referencias y operarios. Se ejecutan después
await Promise.all([seedStock(2_000,session), seedRegistros(10_000,session)])
}, {
// Aqui puedo configurar más la transacción y tipo de sesión
})
} finally {
await session.endSession()
await client.close()
}
}
runSeeds()
Ahora solo hace falta ejecutar el archivo npm start seed:fresh en la raíz del proyecto. Al trabajar con una sesión cualquier error durante la ejecución del seeder revertirá la base de datos a su estado previo.
MongoDB tiene algunas peculiaridades para trabajar con transacciones. Primero hay que tener el servidor configurado como un clúster o replica set y segundo hay que inicializar una sesión y pasarla a todas las operaciones que vamos a realizar con mongo.
Si no el servidor no está configurado como un clúster dará este mensaje de error
MongoServerError: Transaction numbers are only allowed on a replica set member or mongos at Connection.onMessage
Aquí están las instrucciones para convertir un servidor standalone que ya está corriendo en una replica set.
Por otro lado cualquier operación a la que no se le pase la sesión se considera como independiente y funciona independiente a la transacción. Hay que pasarla de la siguiente forma
await db.collection('operarios').insertMany(operariosUnicos,{session})
Las interacciones que se realizan con la base de datos son de naturalez asíncrona. Aprovechamos para lanzar todas las operaciones posibles en paralelo para ganar tiempo.
await session.withTransaction(async () => {
await Promise.all([seedReferencias(3_000, session), seedOperarios(100,session)])
// Dependientes de las referencias y operarios. Por eso se ejecutan después.
await Promise.all([seedStock(2_000,session), seedRegistros(10_000,session)])
}
De esta forma se ejecutan en paralelo a la vez seedReferencia y seedOperarios, en vez de una a una.
Promise.all() espera a que todas las promesas pasadas se resuelvan. Si alguna de ellas no se cumple y es rechazada, al utilizar la sintaxis de async/await, directamente se lanzará una excepción.
Existen otros metodos para manejar de forma más granular el comportamiento de varías promesas concurrentes.
Más detalles de metodos de promesas aquí.
La controversia en torno a FakerJS surgió cuando su desarrollador, Marak Squires, introdujo actualizaciones que sabotearon la biblioteca. Squires, quien también es responsable de otro paquete popular, colors.js, agregó código a estas bibliotecas que causó su mal funcionamiento. En el caso de faker.js, utilizada para generar datos falsos para pruebas de API, Squires eliminó el código y modificó el archivo ReadMe con la pregunta "What really happened with Aaron Swartz?" Esta acción fue en parte una protesta y un comentario sobre la sostenibilidad del desarrollo de software de código abierto y el uso de este software por parte de grandes empresas sin compensación adecuada.