4.7 KiB
#85 — Refonte architecturale : découpage du monolithe & persistance d'état (phase 2)
Statut : livré (T1→T10) —
backend/main.py4 827 → ~750 lignes, 14 routers, persistance partielle (stores verrouillés + rate-limit SQLite optionnel). Méthode : tranches à impact minimal, comportement inchangé, un domaine par commit, suite complète verte à chaque commit (1320 passed / 6 skipped).
1. Découpage du monolithe (T1→T9, comportement inchangé)
Chaque tranche déplace un domaine vers backend/routers/ (handlers verbatim,
mêmes chemins/modèles/auth/tags OpenAPI), les modèles vers backend/schemas.py,
et ne committe que sur suite verte + test_version vert.
| Tranche | Domaine | Nouveau module | Version |
|---|---|---|---|
| T1 | health (/api/health*) |
routers/health.py (+ HealthResponse → schemas) |
2.27.2 |
| T2 | webhooks CRUD | routers/webhooks.py |
2.27.3 |
| T3 | sharing (/api/share*, /s/*) |
routers/sharing.py |
2.27.4 |
| T4 | backups (9 routes) | routers/backups.py (+ Diff/Restore* → schemas, backend/sse.py) |
2.27.5 |
| T5 | search (11 routes) | routers/search.py (+ modèles → schemas, backend/search_executor.py) |
2.27.6 |
| T6a | lecture fichiers | routers/files_read.py (+ modèles, routers/helpers.py) |
2.27.7 |
| T6b | mutations fichiers/dossiers | routers/files_write.py (+ 15 modèles → schemas) |
2.27.8 |
| T6c | media/pdf/export/guide | routers/files_media.py (Range helper → helpers.py) |
2.27.9 |
| T7 | config (12 routes) | routers/config.py (_FALLBACK_MODELS déplacé) |
2.27.10 |
| T8 | vaults + history + conflicts (13 routes) | routers/vaults.py, history.py, conflicts.py (+ backend/watcher_state.py) |
2.27.11 |
| T9 | realtime + render | routers/realtime.py (SSE + collab WS), backend/render.py |
2.27.12 |
main.py ne contient plus que l'assemblage : lifespan, middlewares, montage
des routers, racine /api, statique/SPA, 4 cales de compatibilité testées
(_resolve_safe_path, _backup_file, _check_vault_writable, _get_backup_dir).
Correctifs au passage : décorateur orphelin /s/{token} (double-enregistrement
de /api/conflicts), tag OpenAPI media inexistant (assignation par chemin
conservée), tests statiques frontend réalignés (image-viewer, media-viewer),
tests repointés vers les modules canoniques (test_ai_models, test_api_main).
2. Persistance d'état (T10)
| État | Avant | Après |
|---|---|---|
JTI révoqués (revoked_tokens.json) |
persisté, sans verrou | RLock (load/save/revoke/check) |
shares.json |
persisté, sans verrou | RLock (4 mutateurs) |
webhooks.json + secrets |
persistés, sans verrou | RLock (create/update/delete/secrets) |
api_keys.json (tool-secrets) |
persisté, sans verrou | RLock (set/delete) |
| Rate-limit auth | mémoire, mono-process | inchangé par défaut + option OBSIGATE_RATELIMIT_DB (SQLite WAL : mêmes fenêtres/budgets, partagé multi-workers, survit au redémarrage) |
| Index de recherche | mémoire, rebuild au démarrage | conservé (voir §3) |
users.json, api_tokens.json, vault_settings.json |
déjà verrouillés (BUG-029, #107) | inchangé |
Tests : tests/test_store_locks.py (4 — concurrence threads, pertes prouvées
sans verrou : 25/200 partages), tests/test_ratelimit_store.py (7 —
sémantique SQLite identique, persistance, concurrence 200/200).
Déjà existants et vérifiés (pas de code) : verrous threading + asyncio
de l'indexeur (_index_lock, _async_index_lock), contrat central des
outils IA — backend/tools/registry.py couvre déjà permissions
(requires_vault, require_destructive_allowed), quotas
(check_and_record par outil) et redaction (redact_payload) pour les
35 outils enregistrés via @tool(.
3. Décisions assumées (non fait, et pourquoi)
- Index non persisté sur disque. Le rebuild différentiel (#86 : réutilise
les entrées inchangées
size+mtime) rend le démarrage rapide ; un snapshot introduirait des risques de staleness/drift de format sans gain mesuré. Réévaluer si le démarrage devient lent (vaults 50k+ fichiers). - Redis exclu. SQLite WAL couvre le multi-workers mono-hôte sans nouvelle
infra ; Redis reste l'option multi-nœuds documentée (cf.
ratelimit.py). .gitignore(_*.pyignore les__init__.py). Contourné pargit add -fcomme les packages existants ; assainir la règle à part.- Noms en
_conservés (backend/render.py, stores) : déplacement verbatim, zéro churn d'appels.
4. Reste connu (hors #85)
- CSP
unsafe-inline(migration nonce, BUG-034 partiel) etSecurecookies → #87. main.py(~750 lignes) : lifespan, middlewares, statique/SPA — cible d'extraction ultérieure si besoin, non bloquant.