veilletech.fr
6 sept. Feed du jour
#07 SQLITE Article

Remplacer l'ORM par une classe par table

Douze requêtes par table. Et des centaines de méthodes pour les écrire.

L'auteur de la gem Ruby Extralite défend l'abandon de l'ORM au profit d'une classe par table exposant les quelques requêtes réellement utilisées, écrites en SQL et renvoyant des structures simples. L'argument central : une application ne fait qu'une douzaine de requêtes différentes par table, ce qui ne justifie ni le poids ni le coût d'un constructeur de requêtes générique.

3 min de lectureintermédiairevidéo 1:13
Partager
Sommaire5 sections
  1. Ce qui se passe
  2. Ce que l'ORM ne sait pas dire
  3. Ce qu'il propose à la place
  4. Ce que ça coûte
  5. À retenir

Ce qui se passe

L'auteur du billet a écrit son propre ORM, puis s'en est éloigné. Il maintient aujourd'hui Extralite, une gem Ruby pour SQLite, et défend une position simple : la couche de mapping ligne-vers-objet est une mauvaise abstraction pour des données relationnelles.

Son argument principal n'est pas idéologique, il est quantitatif. Combien de requêtes différentes votre application fait-elle réellement ? Pas d'appels : de requêtes distinctes. Son estimation, tirée des bases de code qu'il a observées, tourne autour d'une douzaine par table. Un constructeur de requêtes générique offre des centaines de méthodes pour produire n'importe quoi ; vous en utilisez douze.

Ce que l'ORM ne sait pas dire

La liste des absences est concrète : fonctions de fenêtrage, CTE, et surtout la clause RETURNING.

L'exemple est parlant. Récupérer des lignes puis les supprimer donne, avec une boucle naïve, un SELECT suivi d'un DELETE par ligne — le N+1 classique. Corrigé en delete_all, on descend à deux requêtes, mais rien ne garantit qu'elles touchent les mêmes lignes hors transaction, et il reste deux allers-retours. Or PostgreSQL et SQLite savent faire les deux d'un coup :

SQL
delete from beads where material = ? returning *;

Une requête, les lignes supprimées et leur contenu renvoyé. L'auteur indique n'avoir trouvé aucune API ActiveRecord pour l'exprimer.

Au coût fonctionnel s'ajoute un coût de masse : il chiffre ActiveRecord à environ 43 000 lignes de code, plus la mémoire et le temps processeur du mapping.

Ce qu'il propose à la place

Une classe par type de données — un PostsStore — qui expose exactement les opérations utiles et renvoie des tables de hachage ordinaires plutôt que des objets d'entité :

Ruby
posts = PostsStore.new(db)
id    = posts.create(title: 'foo', body: 'bar')
post  = posts.by_id(id)
posts.update_by_id(id, title: 'FOO')
posts.delete_by_id(id)
posts.all
posts.all_by_category(category: 'baz')

Vu de l'application, c'est la même chose qu'avant : une interface qui masque la base. La différence est que l'interface est fermée. On ne peut plus inventer une requête au fil de l'écriture d'un contrôleur : il faut l'ajouter à la classe, donc l'assumer et la relire. Les associations ne disparaissent pas pour autant — elles deviennent une méthode qui fait la jointure et renvoie le graphe attendu.

Ce que ça coûte

Le billet est une prise de position, et il faut lire ses limites comme telles. En quittant l'ORM on perd les associations gratuites, les validations, les migrations et le vivier de conventions partagées. Il faut connaître SQL — l'auteur y insiste, non sans une digression assez sévère sur le développement assisté par IA et l'indifférence à la performance.

C'est donc un choix d'architecture, pas une optimisation à appliquer partout. Le terrain d'essai naturel est celui qu'il désigne lui-même : les données qui n'ont jamais été des entités — étiquettes, séries temporelles, journaux d'événements, files de traitement, usages clé-valeur. C'est là que l'objet par ligne coûte le plus et rapporte le moins.

À retenir

La question à se poser avant de choisir un ORM n'est pas « que sait-il faire » mais « de combien de requêtes ai-je besoin ». Si la réponse tient sur une page, l'outil général est peut-être plus lourd que le problème.

Source : Noteflakes