Files
home_stock/docs/adr/0002-architecture-monolithe-modulaire.md
2026-01-28 19:22:30 +01:00

236 lines
10 KiB
Markdown

# 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)