Rafhael Marsigli Logo

Por que eu me divorciei do Laravel Observer

4 min de leitura
Por que eu me divorciei do Laravel Observer

No meu post anterior sobre a migração para DTOs, eu falei sobre como deixei de depender dos “arrays mágicos” do Laravel em favor de tipagem estrita. Esse foi o Passo 1 da minha jornada em direção a um código mais previsível.

Hoje, preciso falar sobre o Passo 2: eu abandonei o Observer.

Por anos, eu fui um verdadeiro Laravel power user. Se a documentação tinha uma feature, eu usava. Observers pareciam um superpoder. Você mantinha seus controllers enxutos e sua lógica “limpa” escondendo tudo dentro de um UserObserver.

Mas conforme meus projetos deixaram de ser simples CRUDs e passaram a lidar com domínios de negócio complexos, percebi que Observers não são clean code. Eles são lógica invisível. E lógica invisível é inimiga da estabilidade.

A ilusão do código “limpo”

O apelo do Observer é óbvio. Você olha para o Controller e ele parece lindo:

public function store(UserData $userData)
{
    // Look how clean! No clutter!
    $user = User::create($userData->toArray());

    return response()->json($user);
}

É bonito, mas traz alguns problemas.

Porque, sem que o desenvolvedor que vai ler esse arquivo daqui a 6 meses saiba, aquela única linha User::create() dispara uma reação em cadeia. Ela envia um e-mail de boas-vindas, cria um Customer ID no Stripe, registra uma atividade e talvez até notifique um canal no Slack.

Isso é o que eu chamo de “Ação Fantasmagórica à Distância” (Spooky Action at a Distance). Você modifica o banco de dados em um lugar, e código é executado em um arquivo que você nem sabia que existia.

A armadilha da ordem de execução

Outro problema enorme dos Observers são as race conditions.

Imagine que você tem uma lógica no evento created que depende de um relacionamento. Se você usa User::create(), o evento created dispara imediatamente — muitas vezes antes de você ter a chance de associar modelos relacionados (como Roles ou Teams).

Você acaba escrevendo código remendado para contornar isso.

public function created(User $user)
{
    // Trying to guess if the relation is loaded yet...
    if ($user->relationLoaded('team')) {
        // ...
    }
}

Isso é frágil.

Sua regra de negócio:

Você não deveria depender da ordem acidental de execução dos eventos do Eloquent

A solução: explícito é melhor que implícito

Assim como eu troquei Form Requests por DTOs para tornar os dados explícitos, troquei Observers por Services (ou Actions) explícitos. Sim, isso significa escrever algumas linhas a mais de código. Mas esse código conta uma história.

Aqui está a versão refatorada do fluxo de criação. Repare que não existe mágica. Você lê o código e sabe exatamente o que acontece.

class CreateUserService
{
    public function handle(UserData $data): User
    {
        return DB::transaction(function () use ($data) {
            // 1. Cria o usuário
            $user = User::create($data->toArray());

            // 2. Trata efeitos colaterais explicitamente
            // Eu vejo isso! Eu sei que isso acontece!
            $this->billingService->createCustomer($user);
            
            // 3. Envia notificações
            // Se eu quiser desativar isso em um import,
            // basta NÃO chamar essa linha.
            // Não preciso usar Model::withoutEvents().
            $user->notify(new WelcomeNotification());

            return $user;
        });
    }
}

Quando Observers SÃO aceitáveis?

Isso significa que Observers são inúteis? De jeito nenhum.

Eu ainda os uso para preocupações puramente técnicas, que sempre devem acontecer, independentemente do contexto, e que não impactam regras de negócio.

Exemplos:

  • Gerar um UUID para um model

  • Limpar chaves de cache

  • Atualizar índices de busca (Elasticsearch / Meilisearch)

Mas para lógica de negócio (enviar e-mails, cobrar cartão de crédito, atribuir times)? Nunca.

Conclusão: maturidade é previsibilidade

Quando somos desenvolvedores júnior, adoramos ferramentas que fazem coisas por nós.

Amamos a mágica.

À medida que nos tornamos seniores (e arquitetos), passamos a valorizar ferramentas que nos permitem dizer exatamente o que queremos que aconteça.

Abandonar Observers foi doloroso no começo. Eu sentia que estava escrevendo “boilerplate”. Mas esse “boilerplate” é, na verdade, documentação viva. É código que meu eu do futuro consegue ler, entender e debugar sem precisar de um mapa de todo o sistema de eventos da aplicação.

Se você está cansado de efeitos colaterais quebrando sua aplicação, pare de depender do evento created. Torne seu código explícito.

Compartilhe com quem você gosta