Django Signals vs Celery Tasks 2026: Quando Usare Quale e Domande per Colloqui

Scopri le differenze tra Django Signals e Celery Tasks, quando utilizzare ciascuno strumento e le domande più frequenti nei colloqui tecnici 2026.

Django Signals vs Celery Tasks 2026: Quando Usare Quale e Domande per Colloqui

Django offre agli sviluppatori numerosi strumenti per gestire la logica basata su eventi e l'elaborazione asincrona. Due dei meccanismi più importanti sono Django Signals e Celery Tasks. Sebbene a prima vista possano sembrare soluzioni simili, differiscono fondamentalmente nella loro architettura e nei casi d'uso.

La differenza fondamentale: Django Signals sono sincroni ed eseguiti nello stesso processo, mentre Celery Tasks vengono eseguiti in modo asincrono in processi worker separati.

Cosa Sono i Django Signals?

I Django Signals implementano il pattern Observer e permettono un accoppiamento lasco tra diverse parti di un'applicazione. Consentono di attivare determinate azioni quando si verificano eventi definiti, senza che il mittente debba conoscere il destinatario.

python
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

Questo esempio mostra un caso d'uso classico: la creazione automatica di un oggetto UserProfile quando viene registrato un nuovo utente.

Signals Integrati in Django

Django fornisce diversi signals predefiniti:

python
# Model Signals
from django.db.models.signals import (
    pre_save,
    post_save,
    pre_delete,
    post_delete,
    m2m_changed
)

# Request/Response Signals
from django.core.signals import (
    request_started,
    request_finished
)

# Database Signals
from django.db.backends.signals import connection_created

Definire Signals Personalizzati

Per casi d'uso specifici è possibile creare signals personalizzati:

python
from django.dispatch import Signal

# Definire il signal
order_completed = Signal()

# Inviare il signal
class Order:
    def complete(self):
        self.status = "completed"
        self.save()
        order_completed.send(sender=self.__class__, order=self)

# Ricevere il signal
@receiver(order_completed)
def send_order_confirmation(sender, order, **kwargs):
    send_email_notification(order.customer.email, order)

Cosa Sono i Celery Tasks?

Celery è una coda di task distribuita che esegue operazioni asincrone in processi worker separati. I task vengono inseriti in una coda messaggi (Redis, RabbitMQ) ed elaborati dai worker.

python
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_welcome_email(user_id):
    from django.contrib.auth.models import User
    user = User.objects.get(id=user_id)
    send_mail(
        subject="Benvenuto!",
        message=f"Ciao {user.username}, benvenuto sulla nostra piattaforma.",
        from_email="noreply@example.com",
        recipient_list=[user.email]
    )

Configurazione di Celery in Django

python
# celery.py
import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

app = Celery("myproject")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

# settings.py
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"

Funzionalità Avanzate di Celery

Celery offre numerose funzionalità avanzate:

python
from celery import shared_task
from celery.exceptions import MaxRetriesExceededError

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def process_payment(self, order_id):
    try:
        order = Order.objects.get(id=order_id)
        result = payment_gateway.charge(order.amount)
        order.payment_status = "completed"
        order.save()
        return result
    except PaymentError as exc:
        try:
            self.retry(exc=exc)
        except MaxRetriesExceededError:
            order.payment_status = "failed"
            order.save()
            raise

# Task con pianificazione
@shared_task
def daily_report():
    generate_and_send_report()

# In celery.py
app.conf.beat_schedule = {
    "daily-report": {
        "task": "myapp.tasks.daily_report",
        "schedule": crontab(hour=8, minute=0),
    },
}

Quando Utilizzare Django Signals?

I Django Signals sono ideali per:

1. Effetti collaterali sincroni all'interno del ciclo di richiesta

python
@receiver(post_save, sender=Order)
def update_inventory(sender, instance, created, **kwargs):
    if created:
        for item in instance.items.all():
            item.product.stock -= item.quantity
            item.product.save()

2. Accoppiamento lasco tra app Django

python
# Nell'app Payments
@receiver(post_save, sender=Payment)
def notify_order_app(sender, instance, **kwargs):
    if instance.status == "successful":
        payment_received.send(sender=sender, payment=instance)

3. Consistenza dei dati nelle modifiche ai Model

python
@receiver(pre_save, sender=Article)
def generate_slug(sender, instance, **kwargs):
    if not instance.slug:
        instance.slug = slugify(instance.title)

Quando Utilizzare Celery Tasks?

Celery è la scelta giusta per:

1. Operazioni di lunga durata

python
@shared_task
def generate_pdf_report(report_id):
    report = Report.objects.get(id=report_id)
    pdf = generate_complex_pdf(report.data)  # Può richiedere minuti
    report.pdf_file.save(f"report_{report_id}.pdf", ContentFile(pdf))
    report.status = "completed"
    report.save()

2. Chiamate API esterne

python
@shared_task(bind=True, max_retries=5)
def sync_with_external_crm(self, customer_id):
    try:
        customer = Customer.objects.get(id=customer_id)
        crm_api.update_contact(customer.to_dict())
    except CRMAPIError as exc:
        self.retry(exc=exc, countdown=2**self.request.retries)

3. Elaborazione massiva

python
from celery import group

@shared_task
def process_single_image(image_id):
    image = Image.objects.get(id=image_id)
    image.create_thumbnails()
    image.optimize()
    image.processed = True
    image.save()

def process_all_images(image_ids):
    job = group(process_single_image.s(id) for id in image_ids)
    result = job.apply_async()
    return result

4. Attività pianificate e ricorrenti

python
@shared_task
def cleanup_expired_sessions():
    from django.contrib.sessions.models import Session
    from django.utils import timezone
    Session.objects.filter(expire_date__lt=timezone.now()).delete()

Combinare Signals e Celery

Una best practice comune è utilizzare i Signals come trigger per i Celery Tasks:

python
@receiver(post_save, sender=Order)
def handle_new_order(sender, instance, created, **kwargs):
    if created:
        # Sincrono: aggiornare dati critici
        instance.order_number = generate_order_number()
        instance.save(update_fields=["order_number"])
        
        # Asincrono: delegare operazioni dispendiose
        send_order_confirmation_email.delay(instance.id)
        notify_warehouse.delay(instance.id)
        sync_with_erp.delay(instance.id)

Pronto a superare i tuoi colloqui su Django?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Tabella Comparativa: Signals vs Celery

AspettoDjango SignalsCelery Tasks
EsecuzioneSincronaAsincrona
ProcessoStesso processoWorker separato
InfrastrutturaNessuna aggiuntivaRichiede message broker
Gestione erroriPropagazione exceptionMeccanismo retry
ScalabilitàLimitataScalabile orizzontalmente
DebugSemplicePiù complesso
LatenzaAumenta tempo rispostaNessun impatto

Errori Comuni e Anti-Pattern

Signals

python
# SBAGLIATO: Operazioni lente nei Signals
@receiver(post_save, sender=User)
def bad_signal_handler(sender, instance, **kwargs):
    send_email(instance.email)  # Blocca la richiesta
    call_external_api(instance)  # Può fallire

# CORRETTO: Usare Celery per operazioni pesanti
@receiver(post_save, sender=User)
def good_signal_handler(sender, instance, created, **kwargs):
    if created:
        send_welcome_email.delay(instance.id)

Celery

python
# SBAGLIATO: Passare istanze Model come parametri
@shared_task
def bad_task(user):
    user.send_notification()  # L'oggetto potrebbe essere obsoleto

# CORRETTO: Passare solo ID
@shared_task
def good_task(user_id):
    user = User.objects.get(id=user_id)
    user.send_notification()

Domande e Risposte per Colloqui

Domanda 1: Qual è la differenza principale tra Django Signals e Celery Tasks?

I Django Signals sono sincroni e vengono eseguiti nello stesso processo del codice chiamante. I Celery Tasks vengono eseguiti in modo asincrono in processi worker separati. I Signals sono adatti per reazioni rapide e interne agli eventi, mentre Celery è progettato per attività di background scalabili e dispendiose in termini di tempo.

Domanda 2: Perché nei signal handler si dovrebbero passare ID invece di oggetti?

Quando si combinano Django Signals con Celery e si passano istanze Model, possono verificarsi problemi: l'oggetto deve essere serializzato, il che può fallire con oggetti complessi. Inoltre, l'oggetto potrebbe essere obsoleto al momento dell'esecuzione del task. Passare ID e ricaricare dal database garantisce dati aggiornati.

Domanda 3: Come si prevengono i loop infiniti con i Signals?

python
@receiver(post_save, sender=Profile)
def update_profile(sender, instance, **kwargs):
    if not getattr(instance, "_skip_signal", False):
        instance._skip_signal = True
        instance.last_updated = timezone.now()
        instance.save(update_fields=["last_updated"])

In alternativa, si può utilizzare update_fields nel metodo save() oppure usare update() invece di save().

Domanda 4: Come si implementa la logica di retry nei Celery Tasks?

Celery offre funzionalità di retry integrate:

python
@shared_task(bind=True, max_retries=3, autoretry_for=(ConnectionError,))
def resilient_task(self, data):
    try:
        process(data)
    except TemporaryError as exc:
        raise self.retry(exc=exc, countdown=60)

Domanda 5: Quando è opportuno definire Signals personalizzati invece di usare quelli integrati?

I Signals personalizzati sono utili quando si devono segnalare eventi specifici del dominio che non sono direttamente correlati alle operazioni sui Model. Esempi: processi aziendali come "ordine completato" o "pagamento ricevuto", che devono essere gestiti da più componenti indipendenti.

Considerazioni sulle Performance

Performance dei Signals

python
# Stabilire la connessione una sola volta
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    def ready(self):
        import myapp.signals  # noqa: F401

Performance di Celery

python
# Task routing per migliori performance
app.conf.task_routes = {
    "myapp.tasks.quick_*": {"queue": "fast"},
    "myapp.tasks.slow_*": {"queue": "slow"},
}

# Regolare il prefetch multiplier
app.conf.worker_prefetch_multiplier = 1  # Per task lenti

Conclusione

Django Signals e Celery Tasks sono strumenti complementari con ambiti di applicazione differenti. I Signals sono eccellenti per scenari di gestione eventi sincroni e interni al processo con accoppiamento lasco. Celery rappresenta la soluzione ottimale per l'elaborazione asincrona e scalabile in background.

L'architettura migliore combina entrambi gli approcci: Signals come trigger leggeri che, quando necessario, avviano Celery Tasks per operazioni pesanti. Questa combinazione garantisce applicazioni reattive e un'elaborazione in background robusta.

Sfida del giorno

Sapresti trovare il bug in Django?

Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 2 settembre 2026

Tag

#django
#signals
#celery
#async
#event-handling

Condividi

Articoli correlati