Django Signals vs Celery Tasks 2026: Wanneer Wat Gebruiken en Sollicitatievragen

Leer de verschillen tussen Django Signals en Celery Tasks, wanneer welke tool te gebruiken en veelgestelde sollicitatievragen voor 2026.

Django Signals vs Celery Tasks 2026: Wanneer Wat Gebruiken en Sollicitatievragen

Django biedt ontwikkelaars talrijke tools voor het afhandelen van event-driven logica en asynchrone verwerking. Twee van de belangrijkste mechanismen zijn Django Signals en Celery Tasks. Hoewel beide op het eerste gezicht vergelijkbare problemen lijken op te lossen, verschillen ze fundamenteel in hun architectuur en toepassingsgebieden.

Het cruciale verschil: Django Signals zijn synchroon en worden binnen hetzelfde proces uitgevoerd, terwijl Celery Tasks asynchroon in aparte worker-processen draaien.

Wat Zijn Django Signals?

Django Signals implementeren het Observer-patroon en maken losse koppeling tussen verschillende delen van een applicatie mogelijk. Ze stellen ontwikkelaars in staat om bepaalde acties te triggeren wanneer gedefinieerde events plaatsvinden, zonder dat de zender de ontvanger hoeft te kennen.

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)

Dit voorbeeld toont een klassiek gebruik: het automatisch aanmaken van een UserProfile-object wanneer een nieuwe gebruiker wordt geregistreerd.

Ingebouwde Django Signals

Django biedt verschillende voorgedefinieerde signals:

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

Aangepaste Signals Definiëren

Voor specifieke toepassingen kunnen eigen signals worden gemaakt:

python
from django.dispatch import Signal

# Signal definiëren
order_completed = Signal()

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

# Signal ontvangen
@receiver(order_completed)
def send_order_confirmation(sender, order, **kwargs):
    send_email_notification(order.customer.email, order)

Wat Zijn Celery Tasks?

Celery is een gedistribueerde taakwachtrij die asynchrone taken uitvoert in aparte worker-processen. Tasks worden in een message queue (Redis, RabbitMQ) geplaatst en door workers verwerkt.

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="Welkom!",
        message=f"Hallo {user.username}, welkom op ons platform.",
        from_email="noreply@example.com",
        recipient_list=[user.email]
    )

Celery Configuratie 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"

Geavanceerde Celery-functionaliteiten

Celery biedt talrijke geavanceerde mogelijkheden:

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 met planning
@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),
    },
}

Wanneer Django Signals Gebruiken?

Django Signals zijn ideaal voor:

1. Synchrone bijeffecten binnen de request-cyclus

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. Losse koppeling tussen Django-apps

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

3. Dataconsistentie bij Model-wijzigingen

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

Wanneer Celery Tasks Gebruiken?

Celery is de juiste keuze voor:

1. Langdurige operaties

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

2. Externe API-aanroepen

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. Bulkverwerking

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. Geplande en terugkerende taken

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()

Combinatie van Signals en Celery

Een veelgebruikte best practice is het gebruik van Signals als triggers voor Celery Tasks:

python
@receiver(post_save, sender=Order)
def handle_new_order(sender, instance, created, **kwargs):
    if created:
        # Synchroon: kritieke data bijwerken
        instance.order_number = generate_order_number()
        instance.save(update_fields=["order_number"])
        
        # Asynchroon: tijdrovende taken uitbesteden
        send_order_confirmation_email.delay(instance.id)
        notify_warehouse.delay(instance.id)
        sync_with_erp.delay(instance.id)

Klaar om je Django gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Vergelijkingstabel: Signals vs Celery

AspectDjango SignalsCelery Tasks
UitvoeringSynchroonAsynchroon
ProcesZelfde procesAparte worker
InfrastructuurGeen extraMessage broker vereist
FoutafhandelingException propagatieRetry-mechanisme
SchaalbaarheidBeperktHorizontaal schaalbaar
DebuggingEenvoudigComplexer
LatentieVerhoogt requesttijdGeen impact

Veelvoorkomende Fouten en Anti-Patterns

Signals

python
# FOUT: Trage operaties in Signals
@receiver(post_save, sender=User)
def bad_signal_handler(sender, instance, **kwargs):
    send_email(instance.email)  # Blokkeert de request
    call_external_api(instance)  # Kan falen

# CORRECT: Celery voor zware taken gebruiken
@receiver(post_save, sender=User)
def good_signal_handler(sender, instance, created, **kwargs):
    if created:
        send_welcome_email.delay(instance.id)

Celery

python
# FOUT: Model-instanties als parameter doorgeven
@shared_task
def bad_task(user):
    user.send_notification()  # Object kan verouderd zijn

# CORRECT: Alleen ID's doorgeven
@shared_task
def good_task(user_id):
    user = User.objects.get(id=user_id)
    user.send_notification()

Sollicitatievragen en Antwoorden

Vraag 1: Wat is het belangrijkste verschil tussen Django Signals en Celery Tasks?

Django Signals zijn synchroon en draaien in hetzelfde proces als de aanroepende code. Celery Tasks worden asynchroon uitgevoerd in aparte worker-processen. Signals zijn geschikt voor snelle, processinterne reacties op events, terwijl Celery ontworpen is voor tijdrovende, schaalbare achtergrondtaken.

Vraag 2: Waarom moet men in signal handlers ID's doorgeven in plaats van objecten?

Wanneer Django Signals gecombineerd worden met Celery en Model-instanties worden doorgegeven, kunnen problemen ontstaan: het object moet geserialiseerd worden, wat bij complexe objecten kan falen. Bovendien kan het object verouderd zijn op het moment dat de taak wordt uitgevoerd. Het doorgeven van ID's en opnieuw laden uit de database garandeert actuele data.

Vraag 3: Hoe voorkom je oneindige loops bij 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"])

Alternatief kan update_fields in de save()-methode worden gebruikt of update() in plaats van save().

Vraag 4: Hoe implementeer je retry-logica in Celery Tasks?

Celery biedt ingebouwde retry-functionaliteit:

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)

Vraag 5: Wanneer moet men aangepaste Signals definiëren in plaats van ingebouwde?

Aangepaste Signals zijn nuttig wanneer domeinspecifieke events gesignaleerd moeten worden die niet direct gerelateerd zijn aan Model-operaties. Voorbeelden: bedrijfsprocessen zoals "bestelling voltooid" of "betaling ontvangen", die door meerdere onafhankelijke componenten moeten worden afgehandeld.

Performance-overwegingen

Signal-Performance

python
# Verbinding slechts één keer maken
from django.apps import AppConfig

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

Celery-Performance

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

# Prefetch multiplier aanpassen
app.conf.worker_prefetch_multiplier = 1  # Voor trage taken

Conclusie

Django Signals en Celery Tasks zijn complementaire tools met verschillende toepassingsgebieden. Signals zijn uitstekend voor synchrone, processinterne event-handling scenario's met losse koppeling. Celery is de optimale oplossing voor asynchrone, schaalbare achtergrondverwerking.

De beste architectuur combineert beide benaderingen: Signals als lichtgewicht triggers die indien nodig Celery Tasks starten voor zware operaties. Deze combinatie zorgt voor responsieve applicaties en robuuste achtergrondverwerking.

Dagelijkse challenge

Zie jij de bug in Django?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 2 september 2026

Tags

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

Delen

Gerelateerde artikelen