Parent : [[INDEX-IA]]
Architecture — Décisions techniques : réversibilité, sur-ingénierie, ADR, microservices
Pattern — Choisir entre 2 solutions : matrice réversibilité × coût
Problème : Débattre indéfiniment entre deux solutions de valeur proche paralyse l'équipe.
Solution : Évaluer sur deux axes : (1) Réversibilité — peut-on revenir en arrière sans réécriture majeure ? (2) Coût du changement futur — si on se trompe, combien ça coûte de corriger ? Préférer systématiquement la solution la plus réversible si les coûts immédiats sont comparables.
Exemple : Choisir entre deux ORMs → choisir le plus "éjectable" (repo pattern). Changer d'ORM dans 18 mois coûtera 2 semaines au lieu de 2 mois.
❌ Sur-ingénierie — architecture pour un problème imaginaire
Problème : Concevoir pour des contraintes qui n'existent pas encore coûte du temps immédiat et génère de la complexité accidentelle.
Solution : Toute décision d'architecture doit être motivée par une contrainte mesurée ou un risque daté — pas par "ça pourrait arriver". L'architecture correcte pour 1000 users est différente pour 1M users.
Exemple : Microservices + Kafka + CQRS pour une app avec 2 devs et 500 users → chaque feature prend 3x plus longtemps, les bugs sont 5x plus durs à tracer.
Pattern — Estimer une complexité honnêtement
Problème : Estimer une feature sans la décomposer produit un chiffre optimiste qui ignore l'intégration, les tests, et les cas limites.
Solution : Décomposer jusqu'à des tâches de 2-4h maximum. Sommer. Multiplier par 1.5 pour les intégrations non anticipées. Multiplier par 1.3 si la zone est peu connue. Ne jamais estimer sans avoir listé les tâches.
Exemple : "Ajouter le login social" estimé à 2 jours → réel : OAuth callback (4h), gestion comptes existants (3h), tests (3h), edge cases token (2h), revue sécu (2h) = 14h minimum.
❌ Choisir une tech par enthousiasme — décision regrettée à 6 mois
Problème : Adopter une technologie émergente pour ses promesses plutôt que sa maturité génère des coûts cachés en maintenance et debugging.
Solution : Pour toute tech en prod, évaluer : (1) Existe-t-il un Stack Overflow pour les erreurs communes ? (2) Y a-t-il des postmortems publics d'usage en prod ? (3) L'équipe peut-elle débugger sans l'auteur ? Si non à l'une des 3 → attendre.
Exemple : Base de données avec 200 GitHub stars pour un projet critique → 6 mois après, bug de corruption, 0 réponse sur les issues, migration d'urgence.
Pattern — Justifier une décision technique à un non-technique
Problème : Expliquer en termes techniques à un décideur non-technique produit une validation sans compréhension.
Solution : Traduire en : (1) Qu'est-ce qui se passe si on ne le fait pas ? (délai/coût/risque mesurable), (2) Quel est le coût de le faire ?, (3) Quand le ROI est-il atteint ? Ne jamais argumenter sur la qualité technique intrinsèque.
Exemple : Au lieu de "on doit migrer vers event-driven" → "aujourd'hui chaque feature prend 3 semaines. Avec cette migration : 3 jours. Coût de migration : 6 semaines. ROI en 3 nouvelles features."
❌ Couplage de la DB au code applicatif
Problème : Du SQL directement dans les controllers devient impossible à refactorer sans toucher des dizaines de fichiers.
Solution : Toujours une couche repository/DAO entre la logique métier et la persistance. La logique métier ne connaît pas le nom des tables ni le SQL — elle appelle getUserById(id).
Exemple : SELECT * FROM users WHERE email = ? dans 34 controllers → changer email en email_address = 34 modifications simultanées.
Pattern — Détecter une décision irréversible déguisée en réversible
Problème : Certaines décisions semblent facilement modifiables mais génèrent des dépendances cachées quasi-irréversibles en pratique.
Solution : Pour toute décision, lister : "Qu'est-ce qui dépendrait de ce choix dans 12 mois ?" Si la réponse inclut des interfaces clients, des contrats API publics, ou du schéma de données avec des millions de lignes → irréversible. Traiter comme telle dès le départ.
Exemple : Choisir le format d'ID (int vs UUID) semble trivial → 18 mois après, IDs exposés en URL, clients qui les stockent, migration impossible sans breaking change.
❌ Microservices par principe, pas par besoin
Problème : Décomposer sans contrainte réelle multiplie la complexité opérationnelle sans bénéfice mesurable.
Solution : Un monolithe bien structuré est préférable à des microservices prématurés. Passer aux microservices quand : (1) différentes parties nécessitent des cycles de déploiement indépendants, (2) des équipes différentes travaillent sur des domaines séparés, (3) des contraintes de scaling hétérogènes sont documentées.
Exemple : 3 microservices pour une app CRUD avec 1 dev → 3 pipelines CI, debugging distribué, latence inter-services. Monolithe modulaire = toutes les features, 1/3 de la complexité.
Pattern — ADR minimaliste : documenter les décisions d'architecture
Problème : Les décisions techniques sans trace écrite se transforment en "personne ne sait pourquoi on a fait ça".
Solution : Architecture Decision Record en 4 champs : Contexte, Décision, Alternatives rejetées (et pourquoi), Conséquences. 1 fichier markdown par décision importante, committé dans le repo. 15 minutes par ADR, économie de plusieurs jours de discussion future.
Exemple : docs/adr/0012-use-postgres-for-audit-logs.md — quand quelqu'un propose de migrer vers ClickHouse 18 mois plus tard, l'ADR rappelle que la contrainte était la conformité GDPR.
Pattern — Évaluer la complexité accidentelle vs essentielle
Problème : Des équipes passent plus de temps à gérer leur propre architecture qu'à résoudre le problème métier.
Solution : Distinguer complexité essentielle (inhérente au problème) vs accidentelle (générée par nos choix). Si > 30% du temps est passé à gérer l'infra plutôt qu'à livrer de la valeur → simplifier avant d'ajouter.
Exemple : Équipe qui passe 40% du temps à gérer les dépendances entre microservices → merge partiel, gain immédiat.