← Journal technique

backend

Prisma, et attraper la dérive de schéma avant qu'elle ne t'attrape

Prisma, et attraper la dérive de schéma avant qu'elle ne t'attrape

schema.prisma est la source unique de vérité de la forme de la base — modèles, relations, index, enums dans un seul fichier — et Prisma en génère un client typé et gère les migrations.

prisma migrate dev      # dev : édite le schéma, obtiens + applique une migration
prisma migrate deploy   # prod/CI : applique seulement les migrations existantes
prisma generate         # régénère le client typé
prisma migrate diff     # compare deux états de schéma — détection de dérive
const app = await prisma.applications.findUnique({
  where: { id },
  include: { children: true, programs: true },
});

La dérive de schéma est le mode de défaillance qui m'a eu

La base ne correspond plus à ce que dit schema.prisma — parce que quelqu'un a édité le schéma et oublié de générer la migration, ou fait un changement manuel directement. Ça échoue silencieusement au début ; le dev local marche encore. Puis ça remonte plus tard, généralement en production, comme une erreur déroutante ou la mauvaise forme de données quelque part sans rapport.

Le correctif qui marche vraiment : faire vérifier par la CI que les migrations reproduisent le schéma à chaque campagne.

prisma migrate diff --from-config-datasource --to-schema database/schema.prisma --exit-code

Si les migrations appliquées ne reconstruisent pas exactement le fichier de schéma, la CI échoue immédiatement — avant que la dérive n'atteigne le staging.

Quelques points pratiques que j'ai rencontrés :

  • Je garde le schéma à Backend/database/schema.prisma avec prisma.config.ts qui pointe là, pas le prisma/schema.prisma par défaut. Entièrement pris en charge, ça vaut la peine de l'écrire clairement parce que chaque tuto suppose le défaut.
  • Avec pgBouncer devant, Prisma a besoin de l'URL poolée pour l'exécution et de l'URL directe pour les migrations, parce que le transaction pooling ne prend pas en charge les fonctionnalités au niveau session dont le DDL a besoin.
  • id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid garde la génération d'UUID cohérente pour les lignes insérées hors de Prisma — SQL brut, scripts de seed, un autre service.

Une chose que j'ai remarquée en reconstruisant l'historique réel de ce projet : chaque fichier de migration est en fait un message de commit daté pour la base. Les noms de migration décrivent ce qui a changé plutôt que d'être enrobés dans un récit de fonctionnalité, donc lire le dossier des migrations chronologiquement est souvent plus précis que le journal git.