import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const systemRoles = [
{ id: 'role-admin', name: 'Administrator' },
{ id: 'role-editor', name: 'Editor' },
{ id: 'role-user', name: 'Regular User' },
];
for (const role of systemRoles) {
await prisma.role.upsert({
where: { id: role.id },
update: { name: role.name },
create: role,
});
}
console.log('Seed-ul a rulat cu succes.');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});Am văzut zeci de proiecte unde comanda prisma db seed e ignorată sau rulată cu frică în CI. Ba crapă din cauza cheilor unice, ba durează minute bune pentru că cineva a vrut să genereze mii de înregistrări cu Faker pentru un test simplu. În articolul ăsta discutăm despre cum facem seed-uri stabile, rapide și, cel mai important, idempotente.
Problema cu Faker și datele random în CI
Faker e excelent pe local când vrei să vezi cum arată layout-ul cu nume lungi sau poze de test. Dar în CI e rețeta perfectă pentru dezastru.
Am pățit asta acum un an la un proiect cu vreo 12.000 de useri în baza de date: build-ul din GitHub Actions pica o dată la zece rulări pentru că Faker genera un email duplicat pe o coloană cu constrângere UNIQUE. Să stai să dai re-run la pipeline-uri doar pentru că ai avut ghinion la generarea de numere random e o pierdere uriașă de timp.
În CI ai nevoie de determinism. Dacă rulezi testele de integrare, vrei ca userul cu ID-ul user-test-admin să existe mereu, să aibă aceeași parolă hash-uită și aceleași permisiuni. Altfel, testele tale devin instabile (flaky) și echipa își pierde încrederea în ele.
Regula de aur: Fără create, doar upsert
Dacă rulezi seed-ul de două ori și a doua oară crapă cu "Record already exists", scriptul tău trebuie rescris. În CI, pipeline-ul poate rula de mai multe ori pe aceeași bază de date (mai ales dacă folosești medii de staging sau preview-uri unde baza nu se șterge la fiecare deploy).
Prisma ne oferă metoda upsert, care verifică dacă înregistrarea există (folosind un câmp unic, de regulă id sau email). Dacă există, face update; dacă nu, o creează.
Uită-te la exemplul de cod de mai jos. Folosim un ID fix, string, în loc de ID-uri autogenerate de bază la fiecare rulare. Astfel, scriptul devine complet idempotent.
Trade-off-ul sincer: Performanța
Trebuie să fim realiști: upsert vine cu un cost.
La un proiect unde aveam de populat vreo 40 de tabele de nomenclatoare, trecerea completă de la createMany la upsert-uri individuale într-un loop a crescut timpul de seed de la 3 secunde la aproape 25 de secunde. Pe local nu îți pasă, dar în CI, dacă ai zeci de build-uri pe zi, secundele astea se adună.
Cum rezolvi asta?
- Faci seed doar pentru datele critice de sistem (roluri, setări globale).
- Pentru datele voluminoase de test, folosești un flag sau o variabilă de mediu (ex:
SEED_HEAVY=true) și le rulezi doar când e neapărat nevoie, eventual folosindcreateManydupă undeleteManype tabelele respective.
Concluzie
Un seed stabil înseamnă ID-uri fixe și funcții de tip upsert. Nu lăsa hazardul să-ți controleze pipeline-ul de CI.
Voi cum gestionați datele de test? Curățați baza complet înainte de fiecare rulare în CI sau vă bazați pe seed-uri idempotente?