ADR-068 : Routes applicatives par contrôleur dans mvc/routes/¶
Statut¶
Acceptée (2026-07-07).
Contexte¶
Le routage applicatif tient aujourd'hui dans un unique fichier mvc/routes.py.
Chaque CRUD généré par forge make:crud y ajoute une dizaine de routes (index, new, create, show, edit, update, destroy, bulk-delete, bulk-delete-confirm, export-csv).
Extrapolé à une application réelle avec de nombreuses entités, mvc/routes.py atteint vite plusieurs centaines de lignes, difficiles à lire et à maintenir.
Forge dispose pourtant déjà d'une convention de regroupement des routes : register_<module>_routes(router), utilisée par les opt-ins (branchés par register_optins(router), ADR-061) et par les routes d'API (register_api_routes(router)).
Il manque son extension aux contrôleurs applicatifs.
Décision¶
Les routes applicatives sont réparties par contrôleur dans un package mvc/routes/, chaque contrôleur exposant sa fonction de branchement, et la racine du package listant explicitement les branchements.
Structure¶
mvc/routes/__init__.pyest la racine de composition : elle créerouter, branche la route d'accueil, appelleregister_optins(router), puis appelle chaqueregister_<controleur>_routes(router).
Le modulemvc.routesreste importable et expose toujoursrouter, doncAPP_ROUTES_MODULE=mvc.routesetmvc.routes.routerne changent pas (le passage de fichier à package est transparent pour le chargeur,core/app/app_factory.py).mvc/routes/<snake>_routes.pycontient, pour un contrôleur, la fonctionregister_<snake>_routes(router)qui déclare ses routes (le blocrouter.group("/<snake>") as g: g.add(...)).
Le package mvc/routes/ remplace le fichier mvc/routes.py : les deux ne peuvent coexister (même nom de module mvc.routes), et le package est la forme retenue.
Branchement explicite, pas d'auto-découverte¶
mvc/routes/__init__.py liste chaque register_<controleur>_routes(router).
Forge n'introduit pas d'auto-découverte (scan de mvc/routes/*.py branché automatiquement) : ce serait de la magie cachée (principe 3) et cela contredirait l'injection de routes par commande explicite (ADR-030).
Le développeur voit d'un coup d'œil, dans un seul fichier, tout ce qui est branché.
Impact sur les générateurs¶
forge make:crudgénèremvc/routes/<snake>_routes.py(write-if-new) avecregister_<snake>_routes(router), et affiche les deux lignes à ajouter dansmvc/routes/__init__.py(l'import et l'appel), au lieu d'afficher le bloc de dix routes.
La racine grossit de deux lignes par contrôleur, pas de dix.forge make:authsuit le même modèle :mvc/routes/auth_routes.py+register_auth_routes(router)(routes/login,/logout).forge make:public-page(et la famillepublic:*) est harmonisé : il génère un fichier de routes du contrôleur des pages publiques et branche de la même façon, plutôt que d'injecter directement dans la racine (évolution de l'injection d'ADR-030/ADR-051 vers le package).
Conséquences¶
- Le routage d'une application reste lisible quel que soit le nombre d'entités : un fichier par contrôleur, une racine qui ne fait que brancher.
- Réutilise et généralise la convention existante
register_<x>_routes(router). - Le squelette livre
mvc/routes/(package) au lieu demvc/routes.py. - Migration d'un projet existant (rupture interne assumée en phase bêta, pas d'utilisateurs externes à protéger) : renommer
mvc/routes.pyenmvc/routes/__init__.py, puis déplacer chaque bloc de routes vers unmvc/routes/<snake>_routes.pyexposantregister_<snake>_routes(router).
La procédure est documentée ; un assistant éventuel fera l'objet d'un ticket à part. routes:listet le chargeur d'application sont inchangés (ils lisentmvc.routes.router).
Charte appliquée¶
- Principe 3 (refuser la magie cachée) et ADR-030 : branchement explicite, jamais d'auto-découverte.
- Principe 8 (noyau minimal) et principe 11 (une seule façon de faire) : une seule convention de branchement,
register_<x>_routes(router), pour les opt-ins, l'API et désormais les contrôleurs applicatifs. - Principe 2 (petits tickets, une responsabilité) : un fichier de routes par contrôleur, une responsabilité par fichier.