Bem-vindo ao Blog da DMarkInfo

Conteúdos e novidades sobre Tecnologia da Informação.

Refatorando Django Signals para Services: Tornando Regras de Negócio Explícitas

Postado por Eduardo Marques em 28/12/2025
Refatorando Django Signals para Services: Tornando Regras de Negócio Explícitas

No artigo anterior, expliquei por que Django Signals costumam ser usados além do que deveriam e como isso acaba escondendo regras de negócio importantes.
Agora vem a pergunta natural:

Se eu não devo usar signals para regras de negócio, o que usar no lugar?

A resposta mais comum em projetos Django maduros é: services.

Neste artigo, vou mostrar como refatorar signals para services, quando fazer essa mudança e como organizar isso de forma limpa, sem reinventar o framework.


O problema prático que estamos resolvendo

Vamos partir de um cenário real (e muito comum):

  • Um post_save em um model

  • Que executa lógica de negócio

  • Criando ou alterando outros objetos

  • Disparando efeitos colaterais importantes

Exemplo clássico (simplificado):

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

Funciona? Sim.
Mas esse código cria alguns problemas claros:

  • A criação do perfil é implícita

  • Quem cria o usuário não sabe que isso acontece

  • Testes ficam mais difíceis

  • A regra de negócio está espalhada

Agora vamos resolver isso de forma mais explícita.


O que são Services no contexto de Django?

Em Django, services não são um recurso oficial do framework.
Eles são um padrão arquitetural.

A ideia é simples:

Colocar regras de negócio em funções ou classes explícitas, chamadas intencionalmente.

Normalmente ficam em arquivos como:

  • services.py

  • use_cases.py

  • domain/services.py

O nome importa menos do que a intenção.


Refatorando um Signal para um Service (passo a passo)

Cenário: criação de usuário com perfil

Antes (com signal)

  • User é criado

  • Um signal cria o perfil automaticamente

  • Quem chama não tem controle

Depois (com service)

Vamos criar um service explícito:

# users/services.py
from .models import User, Perfil
def criar_usuario_com_perfil(**dados):
    user = User.objects.create_user(**dados)
    Perfil.objects.create(user=user)
    return user

 


Agora, no fluxo de cadastro:

user = criar_usuario_com_perfil(
    username="eduardo",
    email="eduardo@email.com",
    password="123456"
)

 

O que mudou?

  • A regra de negócio está visível

  • Não há efeito colateral oculto

  • O código é fácil de testar

  • A leitura fica óbvia


Services deixam o código mais honesto

Uma das maiores vantagens dos services é honestidade do código.

Quando você lê:

finalizar_ordem(ordem)
 

Você espera que:

  • Algo importante aconteça

  • Vários passos ocorram

  • Regras sejam aplicadas

Quando você lê:

ordem.save()
 

Você não espera:

  • Movimentação financeira

  • Atualização de estoque

  • Envio de notificações

  • Geração de relatórios

Services alinham expectativa com realidade.


Refatorando um caso mais crítico: regras financeiras

Vamos para um exemplo mais sensível.

Abordagem com signal (problemática)
@receiver(post_save, sender=OrdemServico)
def gerar_conta_financeira(sender, instance, **kwargs):
    if instance.status == "concluida":
        ContaFinanceira.objects.create(...)

Problemas:

  • Executa em todo save()

  • Pode gerar duplicidade

  • Depende de estado anterior

  • Difícil de testar

Abordagem com service (recomendada)
# ordens/services.py
def concluir_ordem(ordem):
    if ordem.status == "concluida":
        return
    ordem.status = "concluida"
    ordem.save()
    ContaFinanceira.objects.create(
        ordem=ordem,
        valor=ordem.valor_total
    )

Agora:

  • A regra está centralizada

  • Não depende de signal

  • O fluxo é claro

  • O impacto financeiro é explícito


E onde entram os Signals, então?

Depois da refatoração, os signals não desaparecem — eles ficam no lugar certo.

Bons usos após a refatoração:

  • Auditoria

  • Logs

  • Cache

  • Integração externa

Exemplo saudável:

@receiver(post_save, sender=OrdemServico)
def registrar_log(sender, instance, **kwargs):
    registrar_evento("Ordem salva", instance.id)

Note a diferença:

  • O sistema não depende disso para funcionar

  • É apenas um efeito colateral técnico


Como organizar services em projetos maiores

Estrutura comum e funcional:

app/
 ├── models.py
 ├── views.py
 ├── services.py
 ├── selectors.py
 └── tests/

Separação clara:

  • models → estrutura de dados

  • services → regras de negócio

  • views → entrada/saída (HTTP)

  • selectors → consultas complexas

Isso escala muito melhor do que signals espalhados.


Refatorar tudo de uma vez? Não.

Uma armadilha comum é tentar:

“Vamos remover todos os signals do projeto agora.”

Isso quase sempre dá errado.

Abordagem segura:

  1. Identifique signals com regra de negócio

  2. Refatore os mais críticos primeiro

  3. Crie services claros

  4. Migre o uso gradualmente

  5. Deixe signals apenas para efeitos técnicos


Regra prática final

Se você está em dúvida entre signal ou service, use esta regra:

Se alguém precisar chamar isso conscientemente, é um service.
Se alguém não precisar saber que isso acontece, pode ser um signal.

Na maioria dos casos importantes, a resposta será service.


Conclusão

Refatorar Django Signals para Services não é modismo.
É um passo natural de maturidade arquitetural.

O resultado é:

  • Código mais legível

  • Menos efeitos colaterais invisíveis

  • Testes mais simples

  • Menos bugs difíceis de rastrear

Signals continuam existindo — mas deixam de ser protagonistas e passam a ser coadjuvantes técnicos.

E, em projetos reais, isso faz toda a diferença.

Compartilhe este post:
Voltar para a Home