# ADR-0002 — Architecture monolithe modulaire - Statut : accepted - Date : 2026-01-27 --- ## Contexte HomeStock nécessite une architecture logicielle adaptée à un projet mono-utilisateur self-hosted avec les contraintes suivantes : - **Simplicité de déploiement** : L'application doit pouvoir être déployée facilement avec une seule commande - **Maintenance réduite** : Pas d'équipe DevOps, maintenance manuelle par l'utilisateur - **Développement solo** : Un seul développeur, besoin de cohérence et de simplicité - **Évolutivité future** : Possibilité d'évoluer vers multi-utilisateurs sans refonte complète - **Modules logiques distincts** : items, locations, categories, documents, search Le projet démarre avec un périmètre limité (MVP) mais doit pouvoir évoluer progressivement. La question architecturale centrale est : comment organiser le code pour maximiser la maintenabilité tout en gardant une complexité opérationnelle minimale ? --- ## Décision Nous adoptons une **architecture monolithe modulaire** avec les caractéristiques suivantes : ### Définition - **Un seul dépôt Git** (monorepo) contenant backend et frontend - **Un seul processus backend** (serveur FastAPI unique) - **Modules logiques séparés** avec responsabilités clairement définies - **Communication intra-application** via appels de fonctions directs - **Base de données unique** partagée entre tous les modules ### Organisation backend (`backend/app/`) ``` backend/app/ ├── main.py # Point d'entrée FastAPI ├── core/ # Configuration, logging, database │ ├── config.py │ ├── database.py │ └── logging.py ├── models/ # Modèles SQLAlchemy (un fichier par entité) │ ├── item.py │ ├── location.py │ ├── category.py │ └── document.py ├── schemas/ # Schémas Pydantic (validation API) │ ├── item.py │ ├── location.py │ └── ... ├── routers/ # Endpoints API (un fichier par ressource) │ ├── items.py │ ├── locations.py │ ├── categories.py │ ├── documents.py │ └── search.py ├── services/ # Logique métier (orchestration) │ ├── item_service.py │ ├── location_service.py │ └── ... ├── repositories/ # Accès données (abstraction BDD) │ ├── item_repository.py │ ├── location_repository.py │ └── ... └── utils/ # Utilitaires transverses ├── files.py └── search.py ``` ### Organisation frontend (`frontend/src/`) ``` frontend/src/ ├── main.tsx # Point d'entrée React ├── App.tsx # Composant racine + routing ├── components/ # Composants réutilisables │ ├── common/ # Boutons, inputs, modales... │ ├── items/ # Composants spécifiques items │ └── locations/ # Composants spécifiques locations ├── pages/ # Pages complètes (routes) │ ├── Dashboard.tsx │ ├── ItemList.tsx │ ├── ItemDetail.tsx │ └── ... ├── hooks/ # Custom hooks React │ ├── useItems.ts │ ├── useLocations.ts │ └── ... ├── api/ # Client API (fetch/axios) │ ├── items.ts │ ├── locations.ts │ └── ... └── utils/ # Utilitaires helpers └── formatting.ts ``` ### Principes d'organisation 1. **Séparation en couches backend** : routers → services → repositories → models 2. **Dépendances unidirectionnelles** : Les couches supérieures dépendent des inférieures uniquement 3. **Modules auto-contenus** : Chaque module (items, locations, etc.) a ses propres fichiers dans chaque couche 4. **Pas de dépendances circulaires** entre modules 5. **Code partagé** dans `core/` et `utils/` --- ## Alternatives considérées ### 1. Microservices **Description** : Séparer l'application en plusieurs services indépendants (service items, service locations, service search, etc.) **Avantages** : - ✅ Scalabilité horizontale indépendante par service - ✅ Isolation des pannes (un service down ≠ tout down) - ✅ Possibilité d'utiliser des technologies différentes par service - ✅ Déploiement indépendant de chaque service **Inconvénients** : - ❌ Complexité opérationnelle énorme (orchestration, networking, monitoring) - ❌ Nécessite API Gateway, service discovery, circuit breakers - ❌ Communication réseau entre services (latence, sérialisations multiples) - ❌ Transactions distribuées complexes (saga pattern) - ❌ Debugging et traçabilité difficiles - ❌ Overhead infrastructure (plusieurs conteneurs, load balancers, etc.) **Verdict** : ❌ **Rejeté** - Complexité totalement disproportionnée pour un projet mono-utilisateur avec ~1 req/seconde max. Les bénéfices de scalabilité ne s'appliquent pas au cas d'usage. ### 2. Monolithe non structuré (big ball of mud) **Description** : Tout le code dans quelques gros fichiers sans organisation claire **Avantages** : - ✅ Démarrage très rapide - ✅ Pas de contraintes architecturales **Inconvénients** : - ❌ Maintenance cauchemardesque après quelques mois - ❌ Code fortement couplé, difficile à tester - ❌ Évolution impossible sans refonte complète - ❌ Onboarding difficile pour contributeurs futurs **Verdict** : ❌ **Rejeté** - Dette technique garantie, inacceptable même pour un projet solo ### 3. Architecture hexagonale (ports & adapters) **Description** : Isolation stricte du domaine métier avec ports (interfaces) et adapters (implémentations) **Avantages** : - ✅ Isolation forte du domaine métier - ✅ Testabilité maximale (mocks faciles) - ✅ Changement de base de données transparent **Inconvénients** : - ❌ Boilerplate important (interfaces, adapters, DTOs multiples) - ❌ Courbe d'apprentissage élevée - ❌ Overhead pour un projet simple comme HomeStock **Verdict** : ⚠️ **Rejeté mais inspirant** - Trop complexe pour notre besoin, mais on garde l'idée de séparation en couches (repository pattern) ### 4. Monolithe avec modules faiblement couplés (notre choix) **Description** : Un seul déploiement avec modules logiques bien séparés et communication via interfaces claires **Avantages** : - ✅ Simplicité opérationnelle (un seul processus, un seul conteneur) - ✅ Transactions ACID simples (même base de données) - ✅ Pas de latence réseau entre modules - ✅ Refactoring et debugging faciles - ✅ Évolution progressive possible (extraction en microservices si vraiment nécessaire) - ✅ Maintenance réduite **Inconvénients** : - ⚠️ Scalabilité horizontale limitée (non critique pour mono-utilisateur) - ⚠️ Restart complet nécessaire pour tout changement (acceptable) **Verdict** : ✅ **Choisi** - Meilleur équilibre complexité/bénéfices pour notre contexte --- ## Conséquences ### Positives 1. **Déploiement simple** : `docker-compose up` lance tout le système 2. **Transactions ACID** : Toutes les opérations en base profitent des transactions SQLite 3. **Performance** : Pas de latence réseau entre modules, appels de fonctions directs 4. **Debugging facile** : Stack traces complètes, un seul processus à inspecter 5. **Refactoring sans risque** : IDE et linters détectent les erreurs lors de renommages 6. **Tests simples** : Tests d'intégration faciles (toute l'app dans le même process) 7. **Maintenance réduite** : Un seul service à monitorer, logs centralisés naturellement ### Négatives 1. **Scalabilité limitée** : Impossible de scaler un module indépendamment (non pertinent pour mono-utilisateur) 2. **Couplage temporel** : Si un module plante, tout plante (acceptable avec bonne gestion erreurs) 3. **Restart complet** : Modification = restart de toute l'app (dev avec hot-reload, prod peu de déploiements) ### Mitigations - **Pour éviter le couplage fort** : Respecter strictement les couches (routers → services → repositories) - **Pour faciliter évolution future** : Interfaces claires entre modules, pas de dépendances circulaires - **Pour améliorer résilience** : Gestion d'erreurs robuste, retry logic, circuit breaker patterns si nécessaire --- ## Impacts techniques ### Développement - **Pattern 3-layer** : Chaque feature touche 3 fichiers minimum (router, service, repository) - **Imports Python** : Imports absolus depuis `app.` pour éviter imports relatifs fragiles - **Tests** : Tests unitaires par couche + tests d'intégration API end-to-end ### Déploiement - **Docker** : Un seul conteneur backend (+ conteneur frontend séparé pour SPA) - **Scaling** : Scale vertical uniquement (augmenter CPU/RAM du conteneur) - **Rollback** : Simple (redéploiement image Docker précédente) ### Évolution future Si le projet nécessite vraiment des microservices (multi-utilisateurs massif, équipes séparées) : 1. **Extraction progressive** : Modules déjà bien séparés, extraction facilitée 2. **API interne → API externe** : Appels de fonctions deviennent appels HTTP 3. **Base partagée → bases séparées** : Migrations Alembic facilitent séparation BDD Cette architecture n'empêche pas l'évolution, elle la retarde jusqu'à ce qu'elle soit vraiment nécessaire (principe YAGNI = You Ain't Gonna Need It). --- ## Notes Cette décision s'inscrit dans la philosophie "Start with a monolith" popularisée par Martin Fowler et confirmée par de nombreux retours d'expérience (Shopify, GitHub, Stack Overflow ont démarré en monolithe). Pour HomeStock, projet mono-utilisateur avec ~1 req/sec max en pic, un monolithe modulaire est largement suffisant et restera probablement optimal même à long terme. La complexité des microservices n'apporterait aucun bénéfice tangible et augmenterait drastiquement les coûts de développement et maintenance. **Principe appliqué** : "Choose boring technology" - privilégier les architectures éprouvées et simples sur les architectures à la mode mais complexes. --- **Contributeurs** : Gilles (décideur) + Claude Code (architecte) **Références** : - Martin Fowler - "Monolith First" : https://martinfowler.com/bliki/MonolithFirst.html - Sam Newman - "Building Microservices" (recommande de démarrer en monolithe)