Le câblage visible¶
Objectif : poser le fichier qui liste tous les abonnements à la main, et comprendre pourquoi c'est lui, et non le registre, qui fait la valeur de ce parcours.
Ce que vous allez apprendre : la différence entre un registre auditable et un bus magique, et pourquoi elle tient à un seul fichier.
Là où nous en sommes¶
Le palier précédent a posé mvc/events/_registry.py.
Le mécanisme existe, mais personne ne s'abonne encore.
La question est : où les abonnements sont-ils déclarés ?
C'est la seule question qui compte vraiment.
L'ajout¶
D'abord, l'API publique du paquet, mvc/events/__init__.py :
# mvc/events/__init__.py
"""Événements applicatifs : émission et abonnement explicites."""
from mvc.events._registry import emit, listeners_for, subscribe
__all__ = ["emit", "subscribe", "listeners_for"]
Ensuite le fichier central, mvc/events/wiring.py :
# mvc/events/wiring.py
"""Câblage des événements : la liste complète des abonnements.
Ce fichier est la SEULE source de vérité des réactions de l'application.
Le lire de haut en bas répond entièrement à la question
« que se passe-t-il quand tel événement est émis ? ».
"""
from mvc.events._registry import subscribe
def wire_events() -> None:
"""Déclare tous les abonnements. Appelée une fois, au démarrage."""
# Aucun abonnement pour l'instant : le palier suivant en ajoute.
return
Enfin, l'appel au démarrage, dans app.py, à côté de la construction de l'Application :
Comprendre ce code¶
wire_events()est appelée explicitement.
Rien ne la découvre, rien ne la déclenche : si vous ne l'appelez pas, aucun événement n'a d'effet.
C'est voulu.
Un système dont on peut couper tout le comportement en commentant une ligne est un système que l'on comprend.- Le câblage vit dans
app.py, pas dansmvc/.
Ce n'est pas arbitraire : c'est déjà la convention Forge pour les middlewares, que le squelette câble au même endroit avec le même argument.
mvc/porte le code métier,app.pyporte l'assemblage. - Un seul fichier répond à « qui écoute quoi ».
Ouvrezwiring.py, vous savez tout.
C'est exactement la propriété que la découverte automatique détruit.
La variante refusée, et pourquoi¶
Presque tous les frameworks proposent plutôt ceci :
# Contre-exemple : ce que Forge refuse. Ne l'écrivez pas.
@events.on("user.registered")
def send_welcome_mail(data):
...
C'est plus court, et c'est le problème.
Avec le décorateur, l'abonnement est déclaré là où la fonction est définie, donc éparpillé dans tout le projet.
Pour savoir ce qui se passe à l'inscription, il faut désormais chercher dans l'ensemble du code, en espérant que le module contenant le décorateur ait bien été importé.
Cette dernière condition est la source de bugs classique du patron : un écouteur qui ne se déclenche pas parce que son module n'a jamais été chargé, sans la moindre erreur pour vous le dire.
Le wiring.py échange trois lignes de verbosité contre une garantie : si ce n'est pas écrit ici, ça n'arrive pas.
C'est la raison pour laquelle l'ADR-052 place le bus auto-découvert hors trajectoire tout en jugeant recevable « une forme explicite et minimale, registre câblé dans un fichier visible, emit manuel ».
Vous venez d'écrire cette forme-là.
Tester¶
>>> from mvc.events import listeners_for
>>> from mvc.events.wiring import wire_events
>>> wire_events()
>>> listeners_for("user.registered")
[]
La liste est vide et c'est normal : nous n'avons encore abonné personne.
À retenir¶
- Le registre est banal ; le fichier de câblage est ce qui rend le système auditable.
- Le câblage est appelé explicitement au démarrage, dans
app.py, comme les middlewares. - Le décorateur
@events.onest plus court mais rend le comportement introuvable : Forge le refuse par écrit.
Au palier suivant, nous branchons enfin un cas réel.