Signal-based decoupling
Features communicate business events through blinker signals. The emitting feature has zero knowledge of who listens.
Table of contents
The problem
When a user registers through splent_feature_auth, other features need to react:
splent_feature_profile— create a default profilesplent_feature_confirmemail— send a verification email
Without signals, auth would have to import and call those features directly, creating hard coupling.
The pattern
Emitter defines the signal
# splent_feature_auth/signals.py
from blinker import Namespace
auth_signals = Namespace()
user_registered = auth_signals.signal("user_registered")
Emitter sends when the event occurs
# splent_feature_auth/services.py
user_registered.send(current_app._get_current_object(), user=new_user)
Listeners subscribe in their own signals.py
# splent_feature_profile/signals.py
from splent_io.splent_feature_auth.signals import user_registered
@user_registered.connect
def on_user_registered(sender, **kwargs):
user = kwargs["user"]
# Create default profile
...
# splent_feature_confirmemail/signals.py
from splent_io.splent_feature_auth.signals import user_registered
@user_registered.connect
def on_user_registered(sender, **kwargs):
user = kwargs["user"]
# Send verification email
...
Key properties
- Auth has zero knowledge of listeners. It emits; it never imports downstream features.
- Adding or removing a listener requires no changes to the emitting feature.
- Signals are for business logic (user created, order placed). For presentation-layer integration, use template hooks.
Signals vs template hooks
| Mechanism | Layer | Use case |
|---|---|---|
| Blinker signals | Business logic (services) | React to domain events |
| Template hooks | Presentation (Jinja) | Inject HTML into templates |
See also
- Template hooks — presentation-layer integration
- Signals framework page — API reference