Le problème avec les ORM n'est pas qu'ils existent.
Le problème commence lorsqu'une équipe traite l'ORM comme une couche d'abstraction de base de données au lieu d'un générateur de requêtes qui doit encore être compris, mesuré et occasionnellement contourné.
Cette distinction est importante.
## Ce que les ORM savent réellement faire
Les ORM sont bons pour :
- les chemins CRUD standards
- le modélisation de schéma
- les migrations
- la réduction du code de mappage répétitif
Ils sont généralement mauvais pour cacher le modèle de coût des bases de données relationnelles.
Si une équipe ne regarde jamais le SQL, l'ORM devient un générateur de latence avec une belle saisie semi-automatique.
## Le Mode de Défaillance que Tout le Monde Finira par Voir
L'exemple classique est le problème de requête N+1 :
```ts
const users = await db.user.findMany({ take: 50 });
for (const user of users) {
console.log(user.invoices[0]?.status);
}
Au niveau de l'application, cela semble inoffensif.
Au niveau de la base de données, cela signifie souvent :
- une requête pour récupérer les utilisateurs
- cinquante autres requêtes pour récupérer les enregistrements associés
Ce n'est pas un bug de l'ORM. C'est le résultat d'un accès aux relations qui cache les limites des requêtes.
Le Chargement Anticipé n'est Pas une Solution Universelle
La réaction habituelle est de tout charger dès le départ :
const users = await db.user.findMany({
include: {
invoices: true,
},
take: 50,
});
Cela réduit les allers-retours, mais cela peut créer un problème différent :
- des ensembles de résultats beaucoup plus volumineux
- des données dupliquées à travers les jointures
- une reconstruction en mémoire coûteuse
Donc, la vraie leçon n'est pas "toujours charger anticipativement". C'est "comprendre le plan de requête que vous venez de demander".
Quand le SQL Brut ou un Générateur de Requêtes Gagnent
Une fois qu'une requête devient :
- lourde en agrégation
- lourde en jointures
- sensible à la latence
- semblable à un rapport
vous voudrez généralement la façonner explicitement.
Un générateur de requêtes typées est souvent un bon compromis :
const rows = await db
.selectFrom("users as u")
.leftJoin("orders as o", "o.user_id", "u.id")
.select(({ fn }) => [
"u.id",
"u.email",
fn.coalesce(fn.sum("o.total"), 0).as("lifetime_value"),
])
.groupBy(["u.id", "u.email"])
.execute();
La valeur ici n'est pas que le SQL est moralement supérieur. C'est que vous êtes contraint à réfléchir à la structure de la requête.
La Règle Pratique
Utilisez l'ORM là où il aide.
Abandonnez-le lorsque la requête mérite une attention de premier plan.
Les bonnes équipes font les deux :
- ORM pour l'accès aux données routinier
- SQL explicite pour les chemins critiques
- journalisation des requêtes et inspection des plans pour tout ce qui est orienté utilisateur et lent
L'échec d'ingénierie n'est pas d'utiliser un ORM. C'est de renoncer à la responsabilité du SQL qu'il émet.
Lectures Complémentaires