📡
Django Signals — 이벤트 기반 느슨한 결합
post_save, pre_delete가 내부적으로 어떻게 동작하는가
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
User가 생성될 때마다 자동으로 Profile도 생성된다. User 모델 코드를 수정하지 않고도.
내부 구현
django.dispatch.Signal은 내부에 receivers 리스트를 가지고 있다. connect()로 함수를 등록하고, send()로 모든 등록된 함수를 순차 호출한다.
class Signal:
def __init__(self):
self.receivers = [] # [(receiver_key, weakref(receiver)), ...]
def connect(self, receiver, sender=None):
self.receivers.append((key, weakref.ref(receiver)))
def send(self, sender, **kwargs):
for _, receiver in self.receivers:
receiver()(signal=self, sender=sender, **kwargs)
post_save.send(sender=User, instance=user, created=True)가 Model.save() 내부에서 호출된다.
왜 조심해야 하는가
- 암묵적 실행 — 코드에 명시적 호출이 없어서 "누가 이걸 호출하는 거지?"가 된다
- 순서 보장 안 됨 — receiver 실행 순서가 등록 순서에 의존하지만 보장은 아님
- 트랜잭션 문제 — post_save는 트랜잭션 커밋 전에 실행될 수 있다.
transaction.on_commit()사용 권장 - 테스트 복잡성 — Signal이 테스트에서도 발동해서 예상치 못한 부수효과
대안: 명시적 서비스 함수를 호출하는 게 더 추적하기 쉽다. Signal은 "앱 경계를 넘는 느슨한 결합"이 필요할 때만.
핵심 포인트
1
Signal.connect()로 receiver 함수를 등록
2
Model.save() 내부에서 post_save.send() 호출 → 등록된 receiver 전부 실행
3
receiver는 sender, instance, created 등의 인자를 받는다
4
트랜잭션 안전을 위해 transaction.on_commit() 안에서 처리 권장
사용 사례
유저 생성 시 프로필 자동 생성
주문 완료 시 알림 발송 (앱 간 느슨한 결합)