ksergio.com

I love coding

← Volver

Poblando una base de datos con FakerJS

14/1/2024

fakerjs-logo

Poblando datos

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.

Transacciones en Mongo

mongo-logo

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})

Concurrencia de promesas

enter image description here 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í.

Polémica con FakerJS y Marak

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.