3 décisions d'architecture qui ont sauvé mes projets (et celle qui en a tué un)
Faire un choix d'archi en 2 minutes pendant un café peut éviter 6 mois de refacto — ou les déclencher. Quatre vraies décisions, prises sur mes projets en production, avec le coût et le résultat. Sans bullshit, sans 'best practices', juste ce qui marche et ce qui ne marche pas.
Pourquoi cet article
Un développeur senior n'est pas quelqu'un qui sait comment faire les choses. C'est quelqu'un qui sait quoi faire — et surtout quoi ne pas faire.
Je vais raconter 4 décisions d'architecture que j'ai prises sur mes projets en production ces 18 derniers mois. Trois ont rapporté gros. Une m'a coûté 6 semaines.
Les quatre m'ont appris ce que les certifications ne peuvent pas enseigner.
En bref
4 vraies décisions d'architecture prises sur mes projets en production (18 derniers mois) — 3 qui ont rapporté gros, 1 qui m'a coûté 6 semaines. Pas de « best practices » : le coût initial et le résultat réel de chaque choix. Ce que ça montre : un senior se juge à ce qu'il décide de ne pas faire, et à sa capacité à relier un choix technique à son effet sur le prochain changement — pas à l'esthétique du code.
Décision 1 : "Supprimer tous les endpoints REST"
Projet : OneRP (framework temps réel dimensionné pour viser des milliers de connexions concurrentes) Quand : Janvier 2025 Coût initial : 2 jours de réécriture du squelette
Le contexte
OneRP devait synchroniser des données entre un serveur, un client de jeu, et 48 mini-applications React (téléphone in-game, banque, police, etc.). En architecture classique, ça donnerait :
- Un endpoint REST par opération (
GET /api/player,POST /api/banking/transfer, …) - Probablement 200+ endpoints au total.
- Du WebSocket pour le push.
- Chaque écran React qui poll ou s'abonne aux bons events.
C'est ce que j'avais commencé à faire pendant 3 jours. Et c'était l'enfer.
Chaque nouvelle feature demandait :
- Créer la route serveur.
- Définir le DTO de requête + réponse.
- Écrire le client TypeScript.
- Gérer le cache côté client.
- Décider quoi invalider quand quelque chose change.
- Sur quel event WebSocket s'abonner pour le push.
Six étapes pour ajouter une feature. Chacune source de bugs.
La décision
J'ai supprimé tout ça. Plus de routes REST. Plus de DTOs. Plus de cache à invalider.
À la place : un seul canal binaire WebSocket. Chaque "lecture" est une méthode C# sur le serveur :
[ComputeMethod]
public virtual async Task<PlayerInfo> GetPlayer(string id, CancellationToken ct)
=> await _db.Players.FirstAsync(p => p.Id == id, ct);
Le framework (ActualLab Fusion) génère le client TypeScript, gère le cache, et invalide automatiquement tous les écrans qui dépendent de cette donnée quand elle change.
Le résultat
- 0 endpoint REST dans tout le projet.
- Des dizaines de ComputeServices au lieu de 200+ routes à maintenir à la main.
- 0 ligne de code de cache côté client.
- 0 ligne de code d'invalidation manuelle.
- Le chemin de lecture vit dans un cache réactif en mémoire : sur un hit, la réponse est sub-milliseconde côté serveur (ordre de grandeur, pas un benchmark publié).
J'ai répliqué cette archi sur 3 autres SaaS depuis. À chaque fois, plusieurs semaines de gagnées sur le boilerplate. (Le détail technique : Pourquoi j'ai supprimé toutes les API REST de mes SaaS.)
La leçon
Le bon choix d'archi se reconnaît à ceci : plus le projet grandit, moins il y a de code à écrire pour ajouter une feature. Si ton archi te fait payer un coût linéaire par feature, c'est qu'il manque une abstraction quelque part.
Décision 2 : "Faire concourir 12 algorithmes au lieu d'en choisir un"
Projet : SaleCast (forecasting e-commerce multi-marketplaces, mission co-développée — j'ai construit le module de prévision ML et ses connecteurs) Quand : Septembre 2025 Coût initial : 1 semaine pour écrire le moteur de compétition Bénéfice : précision sensiblement meilleure que n'importe quel algo unique
Le contexte
SaleCast doit prédire les ventes produit par produit, sur un catalogue de plusieurs milliers de SKU. Question évidente : quel algorithme de forecasting utiliser ?
J'ai posé la question à 4 data scientists. Quatre réponses différentes :
- "Holt-Winters, classique et solide."
- "LightGBM, gradient boosting bat tout."
- "Prophet, c'est ce que Facebook utilise."
- "TiRex, le nouveau foundation model d'NX-AI."
Personne n'avait raison. Et personne n'avait tort.
Parce qu'aucun algorithme ne gagne sur 100 % des produits. Holt-Winters est bon pour le t-shirt qui se vend tous les jours, catastrophique pour la guirlande de Noël. Croston est conçu pour le produit en déclin lent, ridicule sur le saisonnier.
La décision
Au lieu de choisir, j'ai écrit un moteur de compétition. Chaque nuit, pour chaque produit, 12 prévisionnistes s'affrontent sur des fenêtres de back-test glissantes, et le gagnant remplace l'ancien. Le banc va du baseline statistique aux foundation models de séries temporelles :
- Statistiques / classiques : Naive, Seasonal-Naive, SES, Theta, Croston, TSB, Holt-Winters, MSTL.
- Machine learning : LightGBM (via ML.NET 5.0.0).
- Foundation models : TiRex (ONNX) et Chronos.
- Méta : un Ensemble qui combine les sorties des précédents.
Le moteur tourne dans un host desktop Photino (WebView .NET léger), pas dans une stack mobile.
Le code est moins beau que "je sais que Holt-Winters est le bon choix". Mais c'est mesurablement plus précis sur les vraies données.
Le résultat
Le point qui compte n'est pas un pourcentage précis, c'est la forme du résultat : aucun algorithme ne domine sur tout le catalogue. Le gagnant change selon le profil du produit.
À titre illustratif de ce que la compétition révèle :
- un foundation model comme TiRex rafle une bonne part des produits à pattern riche ;
- LightGBM s'impose sur d'autres ;
- Holt-Winters reste imbattable sur le saisonnier régulier ;
- Croston / TSB prennent le dessus sur l'intermittent.
Le baseline Naive sert surtout de garde-fou : s'il gagne sur un produit, c'est en général le signe qu'il manque de l'historique ou qu'il y a un souci de données — pas qu'on a trouvé le bon modèle.
Le gain réel, ce n'est donc pas un chiffre unique sorti d'un slide, c'est que l'erreur agrégée du système descend nettement sous celle du meilleur algorithme fixe — parce qu'on cesse de payer le coût des produits où ce choix fixe est mauvais.
La leçon
Quand 4 experts ont 4 opinions, c'est rarement parce qu'ils sont incompétents. C'est parce que la bonne réponse dépend du contexte que personne n'a regardé.
Plutôt que de choisir un expert, fais-les concourir sur les vraies données. C'est plus humble, plus précis, et ça marche mieux en production. (J'entre dans le détail du banc d'algos dans SaleCast — la compétition d'algorithmes et les foundation models.)
Décision 3 : "Faire fonctionner l'app sans réseau, comme priorité 1"
Projet : MyRoadTrip (planificateur de voyage collaboratif) Quand : Mars 2025 Coût initial : 2 semaines pour intégrer Yjs (CRDT) Bénéfice : 0 perte de donnée signalée en production
Le contexte
MyRoadTrip est une app de planification de road trip. Le cas d'usage est précis : deux personnes éditent le même itinéraire dans une voiture, à travers les Alpes, sans réseau pendant 3 heures.
L'approche classique :
- Stocker les modifications en local quand offline.
- Au retour du réseau, envoyer la file d'attente.
- Si conflit, demander à l'utilisateur de choisir quelle version garder.
C'est ce que font 95 % des apps. Et c'est exactement ce qu'aucun utilisateur ne veut.
"On a roulé 3 heures, j'ai changé 5 étapes, et l'app me demande maintenant quelle version je veux garder. Mais l'autre conducteur dort. Et je sais plus ce que j'ai changé."
La décision
Pas de file d'attente. Pas de conflit manuel. Pas de notion de "version qui gagne".
À la place : un CRDT (Conflict-free Replicated Data Type) au niveau du champ. Si Alice change le nom de l'étape, et Bob change la durée pendant ce temps, les deux changements passent. Pas l'un ou l'autre. Les deux.
C'est mathématiquement garanti par la structure de données. Pas par un algorithme intelligent.
Le résultat
- 0 conflit signalé par les utilisateurs en 12 mois de prod.
- Compression de 98 % sur les paquets de sync (on envoie les deltas, pas le doc).
- Polling adaptatif : 2s en édition active, 60s sur batterie faible.
J'ai extrait le pattern en bibliothèque réutilisable (@portfolio/crdt-engine).
Je l'ai réutilisé sur 2 autres projets depuis. Coût marginal par projet : 0,5 jour.
La leçon
Quand un user fait face à un conflit, il a déjà perdu. Le bon design ne fait pas "gérer mieux les conflits". Le bon design rend les conflits mathématiquement impossibles.
CRDT, idempotence, additivité, monotonie — la liste des outils existe. Trop peu d'apps les utilisent.
Décision 4 : Celle qui m'a coûté 6 semaines
Projet : GeopolAI (démo R&D locale d'audit de biais IA, non déployée) Quand : Août 2025 Coût : 6 semaines à refaire l'archi
Le contexte
GeopolAI est une démo de recherche que je fais tourner en local : elle compare les réponses de plusieurs modèles (GPT, Claude, Gemini) sur des scénarios géopolitiques et les note sur trois axes — Escalade, Pertes civiles, Realpolitik — pour visualiser leurs biais. Les résultats sont persistés dans PostgreSQL. Tôt dans le projet, j'avais besoin d'une carte du monde animée avec des marqueurs qui apparaissent quand un modèle mentionne un pays.
La mauvaise décision
J'ai choisi D3.js + react-simple-maps + canvas overlays parce que "tout le monde utilise D3 pour les data viz géo".
C'était vrai en 2018. Pas en 2025.
Pourquoi c'était une mauvaise décision
- D3 et React combattent constamment sur qui contrôle le DOM.
- Les animations cartographiques se font dans 3 couches incompatibles (SVG D3 + Canvas + React).
- Le re-render d'un seul marqueur déclenche un re-layout de toute la carte.
- À 12 marqueurs animés en simultané, la page tombait à 15 FPS sur un MacBook M2.
J'ai passé 4 semaines à essayer de réconcilier D3 et React. Hooks pour synchroniser les refs, gestion manuelle du lifecycle, memoization agressive.
Au bout de 4 semaines, j'avais une carte qui marchait. À 22 FPS. Et que je détestais maintenir.
Le pivot
J'ai jeté. 4 semaines de code, à la corbeille.
J'ai recommencé avec un canvas pur + mes propres primitives géographiques. Une seule couche de rendu. Une seule API de mise à jour. Un seul thread de rendu.
2 semaines plus tard, j'avais une carte qui tournait à 60 FPS avec 50 marqueurs animés. Plus simple, plus rapide, plus maintenable.
La leçon
Le réflexe "j'utilise ce que tout le monde utilise" est le piège principal du développeur intermédiaire.
Une lib populaire en 2018 peut être objectivement dépassée en 2025, même si elle est toujours populaire — surtout si elle a 50K stars sur GitHub. Les étoiles ne se déprécient pas avec le temps. Les pratiques, si.
Aujourd'hui, je passe systématiquement 1-2 heures à benchmarker une lib avant de la choisir sur un projet sérieux. Pas pour la performance brute — pour voir si elle se bagarre avec mon framework principal.
Si une lib se bat avec React, Blazor, ou Vue : je passe mon chemin, même si c'est la plus populaire du moment.
Cette habitude m'a fait gagner les 6 semaines que j'avais perdues sur GeopolAI.
Ce que ces 4 décisions ont en commun
Aucune n'était évidente sur le moment. Aucune n'est dans un livre d'architecture. Aucune n'est apparue dans une formation.
Toutes les quatre venaient de questions inconfortables que je me suis posé :
- "Pourquoi est-ce que j'écris autant de boilerplate ?" → suppression REST
- "Pourquoi devrais-je faire confiance à un expert quand il y a 4 experts contradictoires ?" → compétition d'algos
- "Qu'est-ce qui se passe vraiment quand l'utilisateur perd le réseau ?" → CRDT
- "Pourquoi est-ce que je me bats avec ma lib depuis 3 semaines ?" → pivot Canvas
Le développeur senior n'a pas plus de réponses que le junior. Il pose juste des questions plus inconfortables — et il s'autorise à y répondre, même quand la réponse va à l'encontre de ce que "tout le monde fait".
C'est, en une phrase, ce qui sépare les deux.
Florian Sola
Développeur Senior C#/.NET/GenAI — Lead Tech & Architecte logiciel · 9 ans d'expérience
La suite logique
Ce sujet ressemble à ce que vous devez livrer ? Parlons-en.
Je prends des sujets techniques critiques — du cadrage à la production, sans dette ni dépendance après le transfert. Le plus rapide pour voir si ça colle : 30 minutes en visio.
Réponse sous 24 h — souvent bien avant.