Le Club Dorothée et la dette technique invisible

J'avais à peine cinq ans quand j'ai découvert Dragon Ball Z sur le Club Dorothée, un samedi matin sur TF1. Je ne comprenais pas encore très bien ce qui se passait dans les arcs narratifs — les sagas duraient des dizaines d'épisodes et je manquais souvent des semaines entières. Mais quelque chose dans ce format m'a accroché : la progression, la montée en puissance, le fait qu'on pouvait suivre quelque chose sur le long terme et voir qu'on avançait.

Trente ans plus tard, je suis Engineering Manager. Et il y a une certaine ironie à avoir passé ma vie professionnelle à construire des systèmes de suivi — roadmaps, OKRs, burn-down charts — sans jamais avoir construit un système de suivi pour ce qui m'a donné envie de construire des choses.

Alors j'ai construit la Médiathèque.


Ce que j'avais vraiment envie d'apprendre

Ce projet n'est pas né d'un besoin fonctionnel urgent. J'aurais pu utiliser MyAnimeList, Letterboxd, RAWG — des outils qui existent et qui fonctionnent très bien. J'ai choisi de construire le mien parce que les problèmes techniques sous-jacents m'intéressaient.

En particulier, trois choses que je voulais vraiment traverser en pratique :

L'orchestration multi-API. Jikan pour les animés, RAWG pour les jeux, TMDB pour les films et séries — trois APIs avec des structures différentes, des identifiants différents (entier pour MAL, slug texte pour RAWG, entier encore pour TMDB), des conventions différentes. Comment construire une couche d'abstraction propre qui donne une expérience cohérente malgré ça ? C'est exactement le genre de problème qui ressemble à de la plomberie mais qui cache de vraies décisions d'architecture.

Les statistiques pondérées. Compter des entrées est trivial. Mais produire un profil de préférences qui révèle quelque chose de vrai sur ce qu'on apprécie — pas juste ce qu'on a consommé — c'est plus intéressant. Le love_score = count × avg_score est simple comme formule, mais il implique de stocker les bonnes données dès le départ (les genres dénormalisés dans un tableau PostgreSQL, les scores personnels nullable correctement gérés) et de faire les bons trade-offs dans les requêtes SQL.

Rust en conditions réelles. Je voulais mettre les mains dans Rust sur un vrai projet, pas sur des exercices Rustlings.


La décision Rust : praticien ou théoricien

En tant qu'EM, je participe régulièrement à des discussions sur les choix de langage pour des nouveaux services : performance critique, sécurité mémoire, attractivité pour le recrutement, courbe d'apprentissage de l'équipe. Rust revient souvent.

Le problème, c'est que je discutais de Rust principalement comme quelqu'un qui l'a étudié — pas comme quelqu'un qui a eu à se battre avec le borrow checker à 23h sur du code qui semblait parfaitement logique mais ne compilait pas.

La différence est énorme. Quelqu'un qui a lu de la documentation sur Rust peut expliquer conceptuellement ce qu'est un lifetime. Quelqu'un qui a écrit Arc<Mutex<Pool>> dans un handler Axum parce qu'il a mis une heure à comprendre pourquoi ses références de connexion PostgreSQL n'avaient pas la bonne durée de vie — celui-là peut expliquer pourquoi ça fait mal, et dans quels contextes ce mal est justifié par les garanties obtenues.

C'est ce second type de crédibilité que je cherchais. Et c'est ce que j'ai obtenu.


La courbe, honnêtement

Rust est difficile. Pas "difficile comme apprendre un nouveau framework" — difficile comme changer de paradigme mental sur la propriété de la mémoire.

Le compilateur refuse de compiler du code qui, dans n'importe quel autre langage, fonctionnerait parfaitement. Au début, c'est frustrant. Avec du recul, c'est exactement le point : le compilateur élimine à la compilation des bugs qui normalement explosent en production à 3h du matin. Ce qu'il refuse de compiler, c'est du code qui aurait pu créer une data race ou un use-after-free.

La section qui m'a pris le plus de temps : comprendre pourquoi je ne pouvais pas passer ma connexion PostgreSQL directement dans mes handlers de route. Il fallait encapsuler le pool dans un Arc, l'injecter via l'état Axum, et laisser chaque handler extraire sa connexion depuis l'état de la requête. Une fois compris, c'est évident et élégant. Avant de comprendre, c'est une série de messages d'erreur cryptiques.

J'ai réécrit mon handler principal trois fois. C'est le bon nombre.


Les vrais problèmes intéressants

La pagination des épisodes

Jikan renvoie les épisodes 100 par page. Pour un anime comme One Piece (1 100+ épisodes), ça fait au moins 11 appels pour avoir la liste complète. J'ai choisi de ne charger que la première page sur la fiche détail, avec un indicateur has_more. C'est une décision de produit autant que technique : charger 1 100 épisodes dans une liste côté client n'apporte rien à l'utilisateur qui veut juste savoir où il en est.

Les arcs narratifs

Les APIs anime ne retournent pas les arcs — juste les épisodes numérotés. Les arcs, c'est de la connaissance éditoriale, pas de la métadonnée structurée. J'ai choisi de les hardcoder dans un fichier serveur (anime-arcs.ts), indexé par MAL ID. C'est de la dette technique assumée : maintenable manuellement, stable dans le temps (les arcs de Naruto ne vont pas changer), et infiniment plus fiable qu'un scraping de wiki.

En tant qu'EM, ce genre de décision me parle directement. La bonne dette technique, c'est de la dette choisie : on sait ce qu'on sacrifie, on sait pourquoi, et on sait dans quelles conditions ça deviendra un problème. La mauvaise dette, c'est de la dette accumulée sans conscience.

Le love_score

La formule est simple : COUNT(*) × AVG(score). Mais l'implémenter correctement impose de réfléchir à ce qu'on stocke. Les genres ne peuvent pas rester dans une relation séparée si on veut faire des agrégats efficaces — il faut les dénormaliser dans un TEXT[] PostgreSQL. Les scores NULL (entrées non notées) doivent être exclus du calcul de la moyenne, pas traités comme des zéros.

Ces détails semblent mineurs. Ils ont chacun nécessité une décision explicite et un ajustement du schéma.


Ce que j'ai appris sur moi comme praticien

Je code beaucoup moins depuis que je suis EM. C'est inévitable — le calendrier ne ment pas. Ce projet m'a rappelé quelques vérités que je connaissais mais que je n'avais plus ressenties récemment.

Le context switching coûte vraiment cher. Coder seul sur un week-end m'a rappelé à quel point chaque interruption casse quelque chose. Ce que j'accepte parfois trop facilement pour mes équipes — "juste une réunion de 30 minutes au milieu de l'après-midi" — coûte bien plus que 30 minutes. Je le savais. Je le ressens différemment maintenant.

Les edge cases sortent à l'usage, pas à la spec. J'avais prévu un compteur d'épisodes. Je n'avais pas prévu qu'il faudrait qu'il couvre aussi les séries. C'est seulement en ajoutant de vraies entrées séries que j'ai vu que la requête SQL avait un WHERE media_type = 'anime' trop restrictif. Aucune spécification n'aurait capturé ça avant. C'est un argument pour itérer vite sur du réel, pas pour sur-spécifier en amont.

Finir quelque chose a une valeur propre. En management, beaucoup de choses restent toujours "en cours" — les conversations, les initiatives, les changements culturels. Un side project qui démarre et qui se déploie, c'est une boucle qui se ferme. C'est rare et ça fait du bien.


Et maintenant

La médiathèque tourne. Les fiches se construisent. Les statistiques reflètent quelque chose de vrai sur trente ans d'animés regardés depuis un canapé, un samedi matin, devant le Club Dorothée.

Le prochain chantier technique sur lequel j'ai envie de mettre les mains : les modes d'édition d'image avancés (outpainting, inpainting) sur ImagiChet. Parce que les side projects ont cette propriété : ils génèrent leurs propres questions suivantes.