SQL est le contrat qui survit à votre ORM

2026-09-10

Chaque produit que j’ai livré a survécu à au moins un choix de framework. La base de données leur a tous survécu. Les versions de Next.js changent, l’ORM est remplacé, l’équipe tourne ; les lignes restent, avec toutes les hypothèses inscrites dans le schéma. C’est pourquoi SQL est, à mes yeux, le véritable contrat du produit, pas un détail d’implémentation caché derrière une classe repository.

Prisma couvre une grande partie de mon quotidien : CRUD typé, migrations dans le dépôt et schéma lisible par une nouvelle recrue. Mais je lis aussi le SQL qu’il génère. Si une page de liste lance une requête par ligne, si un rapport exige une fonction de fenêtre ou si un tableau de bord a besoin d’un GROUP BY sur trois jointures, j’écris la requête avec $queryRaw ou je la place dans une vue. Avant d’ajouter Redis, j’ouvre EXPLAIN ANALYZE. La plupart des tickets « il nous faut un cache » cachent un index manquant.

Les contraintes sont l’assurance qualité la moins chère qui soit. NOT NULL, UNIQUE, FOREIGN KEY et CHECK s’appliquent à chaque écriture, depuis chaque service, y compris depuis le script lancé manuellement un vendredi. Zod protège l’API ; la base protège la vérité. L’argent est stocké en unités mineures entières ou en NUMERIC, jamais en float. Les webhooks de paiement arrivent parfois deux fois : la clé d’idempotence mérite donc un index unique, pas un commentaire dans le handler.

Les problèmes de concurrence sont à l’origine de nombreux bugs FinTech. Deux requêtes lisent un solde, constatent toutes deux qu’il est suffisant et écrivent toutes deux : c’est une mise à jour perdue, qu’aucune validation frontend ne peut corriger. Je préfère une instruction atomique — UPDATE ... SET balance = balance - $1 WHERE id = $2 AND balance >= $1 — puis vérifier qu’une seule ligne a changé. Autre option : une transaction avec SELECT ... FOR UPDATE, accompagnée d’une table de ledger en ajout seul pour expliquer chaque mouvement par la suite. Sur des systèmes ayant traité plus de 1,5 million de dollars, pouvoir « expliquer après coup » constitue l’essentiel du travail.

Les index suivent les requêtes réellement exécutées, pas les colonnes qui semblent importantes. Dans un index composite, les colonnes testées par égalité viennent d’abord, la plage ou le tri à la fin ; les index partiels conviennent aux lignes « actives » et la pagination par clé est préférable à OFFSET 50000. Sur une table en production, les migrations se font par étapes : ajouter la colonne nullable, la remplir par lots, puis ajouter la contrainte ; construire l’index avec CONCURRENTLY. Une migration qui verrouille la table des commandes à midi, c’est une panne à laquelle on a joint une pull request.

Je refuse la table entity-attribute-value dite « flexible » à la place d’un schéma, JSONB comme cachette pour des champs métier essentiels que personne ne veut modéliser, les soft deletes généralisés sans réflexion sur les contraintes d’unicité, et un cache placé devant une requête que personne n’a analysée avec EXPLAIN. Envoyez-moi la requête et le nombre de lignes avec votre page lente ou votre schéma incertain. Je vous répondrai avec le plan, l’index et la première contrainte que j’ajouterais.

Donnez à votre prochain produit quelqu’un qui reste.

Prêt à commencer ? Écrivez-moi — je peux démarrer immédiatement et rester dans la durée. Des collaborations à long terme, pas une passation après deux semaines. Discutons de votre projet, de vos exigences et de la meilleure façon d’avancer.

Posez une question sur mon expérience ou mes services