Quem desenvolve com Django por algum tempo inevitavelmente se depara com eles: Django Signals.
Eles parecem elegantes, “automáticos” e até mágicos no começo. Mas, com o tempo, muitos desenvolvedores passam a evitá-los quase como uma regra.
Afinal: signals são uma má ideia?
Ou o problema é como (e quando) eles são usados?
Neste artigo, vou explicar o que são signals, por que eles causam tantos problemas, quando fazem sentido e quando devem ser evitados a qualquer custo — com exemplos reais de projetos Django.
O que são Django Signals, afinal?
Signals são um mecanismo de notificação do Django.
Eles permitem que você execute código automaticamente quando determinado evento acontece, sem acoplar diretamente quem dispara o evento a quem reage a ele.
Exemplos clássicos:
-
Um objeto foi salvo (
post_save) -
Um objeto foi deletado (
post_delete) -
Um usuário fez login (
user_logged_in)
Na prática, você diz algo como:
“Quando isso acontecer, execute essa função.”
Isso parece ótimo. E, conceitualmente, é mesmo.
Por que Django Signals parecem uma ótima ideia no começo?
Signals costumam ser introduzidos assim:
“Quando o usuário for criado, crie automaticamente o perfil.”
Ou:
“Quando uma ordem de serviço for concluída, gere um registro financeiro.”
Esses exemplos funcionam e são muito tentadores porque:
-
Reduzem código repetido
-
Centralizam comportamentos
-
“Automatizam” regras de negócio
Em projetos pequenos, isso geralmente não dá problema imediato.
O problema aparece quando o projeto cresce.
O verdadeiro problema dos Django Signals
O maior problema dos signals não é técnico.
É arquitetural e cognitivo.
-> Código que executa “do nada”
Quando você lê uma view ou um serviço e vê:
Nada ali indica que:
-
Um perfil será criado
-
Um email será enviado
-
Um log será gerado
-
Um registro financeiro será criado
Essas ações estão ocultas, espalhadas em arquivos de signals.py.
Isso quebra um princípio fundamental:
Código deve ser previsível ao ser lido
Dificuldade de rastrear bugs
Imagine o cenário:
-
Um objeto está sendo criado
-
Algo inesperado acontece
-
Um efeito colateral surge
Pergunta clássica:
“Quem executou isso?”
Resposta frequente:
“Ah… deve ser algum signal.”
Esse tipo de bug:
-
É difícil de debugar
-
Não aparece claramente no stack trace
-
Depende da ordem de importação dos apps
Dependência implícita entre apps
Signals criam acoplamento invisível.
Um app depende de outro:
-
Sem importar diretamente
-
Sem declarar dependência
-
Sem deixar claro no código
Isso dificulta:
-
Reuso de apps
-
Testes isolados
-
Remoção ou refatoração de funcionalidades
Problemas em testes automatizados
Em testes, signals costumam:
-
Criar dados inesperados
-
Executar lógica que você não queria naquele teste
-
Obrigar a usar
disconnect()ou mocks
Isso deixa os testes:
-
Mais frágeis
-
Mais lentos
-
Menos previsíveis
Então… Django Signals são uma má ideia?
A resposta curta é:
Não. Mas são usados errado na maioria das vezes.
Signals não foram feitos para:
-
Regras de negócio complexas
-
Fluxos críticos da aplicação
-
Ações que precisam ser claramente visíveis
Eles funcionam melhor como:
Reação técnica a eventos técnicos
Quando Django Signals FAZEM sentido
Auditoria e logging
Signals são ótimos para:
-
Logs
-
Auditoria
-
Histórico de alterações
Exemplo:
-
Registrar quem alterou um objeto
-
Salvar histórico de mudanças
-
Notificar sistemas externos
Aqui, o efeito colateral é aceitável e até desejado.
Sincronização com sistemas externos
Exemplo:
-
Enviar evento para fila
-
Atualizar cache
-
Notificar webhook
Essas ações:
-
Não alteram o fluxo principal
-
Não mudam regras de negócio
-
Não interferem diretamente na lógica da aplicação
Funcionalidades opcionais ou desacopladas
Exemplo:
-
Plugin
-
App reutilizável
-
Feature que pode ser desligada
Signals funcionam bem quando:
-
O core do sistema não depende deles
-
Eles apenas “escutam” eventos
Quando você DEVE evitar Django Signals
Regras de negócio
Se algo é regra de negócio, ela deve estar:
-
Em services
-
Em métodos explícitos
-
Em camadas claras
Exemplo ruim:
“Quando uma ordem for salva como concluída, gere a conta financeira via signal.”
Exemplo melhor:
Aqui, quem lê o código sabe exatamente o que acontece.
Fluxos críticos da aplicação
Se a ação:
-
Impacta dinheiro
-
Impacta permissões
-
Impacta dados sensíveis
Ela não deve acontecer escondida em um signal.
Criação automática de objetos essenciais
O clássico:
“Criar perfil automaticamente ao criar usuário”
Funciona? Sim.
É a melhor abordagem? Nem sempre.
Alternativa melhor:
-
Criar explicitamente no fluxo de cadastro
-
Ou encapsular em um serviço de domínio
Uma abordagem mais madura: Services ao invés de Signals
Em projetos profissionais, é comum adotar:
-
services.py -
use_cases.py -
domain/
Exemplo conceitual:
user = User.objects.create_user(**dados)
Perfil.objects.create(user=user)
enviar_email_boas_vindas(user)
return user
Aqui:
-
Tudo é explícito
-
Tudo é testável
-
Nada acontece “magicamente”
Regra prática para decidir
Antes de usar um signal, faça esta pergunta:
Se alguém ler esse código daqui a 6 meses, vai entender que isso acontece?
Se a resposta for não, provavelmente não é um bom caso para signal.
Django Signals não são vilões, mas também não são solução padrão.
Eles funcionam bem quando:
-
Lidam com efeitos colaterais técnicos
-
Não escondem regras de negócio
-
Não controlam fluxos críticos
Eles se tornam um problema quando:
-
Assumem papel central na lógica da aplicação
-
Criam dependências invisíveis
-
Tornam o sistema difícil de entender e manter
Usar signals com maturidade é saber quando NÃO usá-los.
E, na maioria dos projetos reais, menos signals significam mais clareza, mais controle e menos bugs.