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 profile
  • splent_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


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es