generated from gilles/template-webapp
236 lines
10 KiB
Markdown
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)
|