Autoblog de sametmax.com

Ce site n'est pas le site officiel de sametmax.com.
C'est un blog automatisé qui réplique les articles de sametmax.com

Les critiques des ORM sont à côté de la plaque 30

Fri, 29 Dec 2017 10:31:05 +0000 - (source)

En ce moment, y a deux modes. Dire que les cryptomonnaies c’est génial, et dire que les ORM c’est de la merde.

Durant les derniers articles, je pense qu’on vous a assez parlé de crypto, donc on va parler des ORM.

Ou ORMs. Je sais jamais si les acronymes s’accordent.

Rappel: qu’est-ce qu’un ORM ?

Si vous avez lu le titre de l’article et que votre sang n’a fait qu’un tour, vous pouvez passer tout de suite à la partie suivante, puisqu’on va commencer par les révisions.

Un ORM, pour Object-relational Mapping, est une bibliothèque qui permet de décrire son modèle de données, et d’utiliser cette description pour faire le lien entre votre application et une base de données relationnelle. Dans sa forme la plus courante, l’ORM fournit les outils pour générer des requêtes SQL – sans écrire du SQL – et les exécuter, mais présente également les résultats sous forme d’objets du langage dans lequel il est écrit.

Par exemple, en Python, je peux faire de la base de données à la mano (sans ORM):

import sqlite3
 
# Création de la base
with sqlite3.connect('db.sqlite') as conn:
 
    # On se positionne sur la base
    c = conn.cursor()
 
    # Créer la table
    c.execute('''
        CREATE TABLE product (
            name text,
            price real
        )
    ''')
 
    # Insertion de données en base
    c.execute("INSERT INTO product VALUES ('Pizza', 5000)")
    c.execute("INSERT INTO product VALUES ('Love', 150)")
    c.execute("INSERT INTO product VALUES ('Nessie', 3.5)")
 
    # Sauvegarde des changements
    conn.commit()
 
    # Lecture de toute la base:
    for row in c.execute('SELECT * FROM product'):
        print(type(row), ':', *row)

Ce qui va me sortir:

python3 db.py
< class 'tuple' > : Pizza 5000.0
< class 'tuple' > : Love 150.0
< class 'tuple' > : Nessie 3.5

Et voici la même chose avec l’ORM peewee (apres pip install peewee :)):

import peewee as pw
 
# On créé la base
db = pw.SqliteDatabase('product2.db')
 
# Description de la table.
class Product(pw.Model):
 
    name = pw.CharField()
    price = pw.FloatField()
 
    class Meta:
        database = db
 
# Connection et création de la table
db.connect()
db.create_tables([Product])
 
# Insertion de données en base
Product.create(name='Pizza', price=5000)
Product.create(name='Love', price=150)
Product.create(name='Nessie', price=3.5)
 
for p in Product.select():
    print(type(p), ':', p.name, p.price)
 
db.close()

Ce qui sort:

python3 db.py
< class '__main__.Product' > : Pizza 5000.0
< class '__main__.Product' > : Love 150.0
< class '__main__.Product' > : Nessie 3.5

A priori, c’est la même chose, mais avec un style différent. Le premier exemple favorise l’écriture du SQL à la main, fait les requêtes explicitement et récupère les résultats sous forme de tuples. Le second a une surcouche, l’ORM, qui implique que tout est décrit en Python. Le SQL est généré sous le capot, et on récupère les résultats sous forme d’objets Product.

Il existe de nombreux ORM en Python, les plus populaires étant Peewee (pour les petits besoins), SQLAlchemy (pour les gros projets) et l’ORM de Django (ben, si vous utilisez Django ^^). Évidemment le concept des ORM n’est pas particulièrement lié à Python, et on en retrouve dans tous les langages, avec des variations de styles et de fonctionnalités.

Que reproche-t-on aux ORM ?

Avant de répondre aux détracteurs, listons d’abord leurs arguments.

Et vous savez quoi ?

Ils ont raison.

Heu ?

Toutes ces critiques sont parfaitement justifiées.

Sauf qu’elles passent complètement à côté de la problématique.

Les ORM ne servent pas à éviter d’écrire du SQL.

Ça, c’est vaguement un effet de bord.

Ils servent à créer une API unifiée inspectable, une expérience homogène, un point d’entrée unique, un socle de référence explicite et central, pour le modèle de données.

Une fois qu’on a passé pas mal de temps à faire des projets, on note toujours la même chose: certaines parties du logiciel sont réécrites encore et encore. Si un projet commence à parler à une source de données à la main (API, base de données, crawling, fichiers, etc), tôt ou tard, quelqu’un dans l’équipe va commencer à écrire une abstraction. Celle-ci va grossir, et finir par implémenter au bout de nombreux mois, pour l’accès à la base de données, un semi ORM dégueulasse, mal testé / documenté.

Mais, si les requêtes SQL à la main c’est si bien, alors pourquoi ça arrive ?

Simplement parce que tout projet qui grandit a besoin d’une forme de gestion de la complexité. Ca passe par avoir un point, et un seul où sont décrites à quoi ressemblent les données, leurs accès, leurs garanties, leurs contraintes, et leurs validations. Ca passe aussi par permettre aux autres parties du projet d’utiliser automatiquement ces informations pour leur code métier, ainsi que la logique associée.

L’exemple de Django

L’ORM Django n’est pas vraiment le projet Python le plus propre. Il a plein de limitations et bizarreries, et son principal concurrent, SQLAlchemy est probablement une des meilleures libs au monde dans cette spécialité.

Mais !

Il est au coeur de ce qui a fait autant le succès colossal de Django.

Parce que Django est organisé autour de son ORM, il peut proposer:

A cela se rajoute le fait que tous les projets Django se ressemblent. Il est très facile de passer d’une équipe à une autre, d’un projet à une autre. La formalisation du schéma devient une documentation, et la seule source de la vérité à propos des données, et pas juste celle de la base. Et qui est commité dans Git. Pour savoir ce que fait un projet Django, il suffit de regarder urls.py, settings.py et models.py, et c’est parti.

Mais ce n’est pas tout. L’ORM ne fait pas que définir un point central inspectable et des outils réutilisables. Il donne aussi une base commune sur laquelle tout le monde peut compter.

Et pour cette raison, l’écosystème Django est très, très riche en modules tierces partis qui se pluggent en 3 coups de cuillère à pot:

La cerise sur le gâteau ? Parce que tout ça utilise l’ORM, tout ça est compatible ensemble. Votre authentification social auth va produire un objet user qui pourra se logger, consulter un dashboard qui contiendra le résultat d’un sondage sur un produit de la boutique.

Et ça marche avec MySQL, Oracle, SQLite ou PosgreSQL, comme l’intégralité du framework, gratos.

Ce n’est pas l’apanage de Django hein, RoR fait pareil.

Maintenant prenez un projet NodeJS. Pour le coup, pas parce que “JS ça pue” mais parce que la culture de l’ORM n’est pas très présente dans cette communauté. Non pas que ça n’existe pas, mais il n’y a pas de Django du monde du Javascript. Même les gros framework type Meteor n’ont rien d’aussi intégré.

Vous allez devoir réapprendre toute la mécanique de gestion de données si vous changez de projet, car ça sera fait différemment à chaque fois. Vous allez devoir former des gens.

Et surtout vous allez devoir réécrire tout ça.

Oh bien sûr, vous aurez une bibliothèque pour chaque chose, mais elle sera écrite différemment. Vous n’aurez pas d’objet User sur qui compter. Votre moyen de traduire le texte ? Pas le même. Vous utilisez Oracle mais l’auteur PostgreSQL ? Pas de bol. Vous voulez générer quelque chose à partir de votre modèle de données ? Ah, pourquoi vous croyez que facebook a créé GraphQL ! Une petite migration ? Vous avez le choix de l’embarras. Bon, maintenant vous allez gérer les dates, et 4 de vos bibliothèques les sérialisent différemment et une utilise l’heure locale au lieu d’UTC.

Évidemment, on sait que votre équipe ne testera pas tout ça, et ne documentera pas tout ça. La suivante non plus.

Donc non, l’ORM, ce n’est pas parce que “mais heu SQL c’est dur”.

C’est parce que ça permet de créer un monde autour.

Objection !

On a des abstractions qui ne sont pas des ORM…

L’important est d’avoir un modèle central, pas un ORM. Mais les ORM font ça particulièrement bien.

Il existe des abstractions (ex: LINQ) qui font un excellent travail pour masquer la source de données. Mais elles ne sont pas un remplacement pour un modèle introspectable central listant nature et contraintes.

Une bonne lib propose les deux.

Par exemple SQLALchemy propose un ORM, mais en vérité l’API de base est fonctionnelle et composable. Pour cette raison, on peut utiliser toutes fonctionnalités avancées de sa base de données avec SQLAlchemy car on a cette alternative à l’ORM à tout moment, et qui est compatible avec l’ORM.

Mais les perfs !

D’abord, on optimise pour les humains. En chemin, on optimise pour la machine. Quand ton ORM arrête de scaler, c’est un BON problème à avoir. Ca veut dire que le projet a atteint un certain succès, et qu’on peut investir dans la séparation avec l’ORM.

De plus, aucune technologie n’est faite pour être utilisée partout, tout le temps, pour tout le projet.

Google a connu ses débuts de succès en Python, et avec sa taille, a réécrit une partie en Java, C puis Go. C’est logique, on ne commence pas avec un langage bas niveau directement, c’est trop lent à écrire. Mais on ne garde pas un langage haut niveau pour tout quand ça monte dans les tours. Enfin les tours… Là on parle de centrifugeuse cosmique hein.

Car gardez en tête que vous n’êtes PAS Google. L’immense majorité des projets deviennent viables, rentables, puis pleins de succès, sans jamais atteindre l’échelle qui amène aux limites de l’ORM.

Quant à l’idée que votre stagiaire peut écrire une boucle avec une requête à chaque step… Il peut tout aussi bien écrire une requête SQL sans se protéger correctement l’injection de code. C’est con un stagiaire, faut le surveiller. C’est le principe, sinon il aurait un CDI.

Mais les fonctionnalités !

Les ORM bien faits n’empêchent pas d’utiliser toutes les fonctionnalités de son système. Et tous permettent d’écrire du SQL à la main si besoin. C’est comme les blocks unsafe de rust: on empêche pas, on isole.

L’idée c’est de garder ça pour le moment où on en a vraiment besoin. SQL est à la base de données ce que l’assembleur est à la machine. On n’écrit pas tout en assembleur de nos jours, ça n’est pas utile.

Root of all evil, tout ça.

Mais on ne change pas de base de données souvent !

L’argument “l’orm supporte plusieurs bases” n’est pas destiné à la migration de données d’un projet existant d’une base de données à une autre.

Pas. Du. Tout.

C’est pas que ça arrive jamais. Ça m’est déjà arrivé 2, 3 de fois.

Mais ce n’est pas le cas courant. Le cas courant c’est la réutilisation du code d’un projet précédent dans un nouveau projet utilisant une base de données différente. C’est la création d’un écosystème de modules qui ne sont pas dépendants de la base de données.

Si vous faites une “app” Django, vous pouvez la publier et elle sera utile pour toutes les bases de données supportées. Et c’est pour ça qu’il y autant d’outils.

Mais on pourrait avoir un modèle central sans ORM !

Oui, mais toutes les formes ne se valent pas.

Par exemple, Doctrine permet d’écrire son modèle dans un fichier YAML, et Hibernate dans un fichier XML.

Au final on écrit son modèle dans un langage moins complet, moins expressif, moins facile à débugger et avec moins de tooling. On perd encore plus en faisant ça qu’en passant de SQL à un ORM, sans autant de gains.

En prime, on peut vouloir de la logique de validation très complexe ou des choses à faire hors validation (signals, génération dynamique de modèle, etc), et là, pouet.

Une alternative, ça serait de se servir d’une lib de pur modèle (ex: l’excellent marshmallow) et de tout dériver de là. Une approche intéressante qui pourrait satisfaire tous les camps, mais que je n’ai jamais vu poussée jusqu’au bout dans aucun framework. Si vous cherchez un projet pour vos week-end :)

Lib VS framework

C’est un peu le vieux débat du découplage VS intégration qu’on retrouve dans la critique des ORM (ou dans vi VS vscode, POO vs fonctionnel, ta femme vs ta mère…).

Et comme d’habitude on retrouve toujours les meilleurs programmeurs du côté de ceux qui veulent le plus de liberté (vive SQL!) parce qu’ils ignorent complètement les problématiques qui vont plus loin que leur fichier de code. Faire fleurir un écosystème, gérer une communauté, favoriser la productivité, facilité l’intégration des membres de ses équipes… Tout ça sont des problématiques moins funs que de faire la requête parfaite avec le tout nouveau champ hyperloglog de PostGres.

Difficile de convaincre des gens qui sont non seulement excellents, mais qui sauront, seuls, être très productifs sans ORM. Surtout quand les gros projets qui atteignent des centaines de millions d’utilisateurs par jour finissent toujours par se séparer de leurs abstractions initiales.

Mais voilà, il ne faut pas perdre de vue que 2 projets sur 3 en informatique, échouent. Quasiment jamais pour des raisons techniques. Généralement la cause est humaine. Et l’ORM est là pour soutenir l’humain en créant un pivot sur lequel il peut compter, quitte à investir plus tard pour s’en passer.

C’est un excellent outil et une très belle réussite de l’informatique moderne. Si on sait l’aborder sans dogmatisme.


Powered by VroumVroumBlog 0.1.31 - RSS Feed
Download config articles