Dominando o Desacoplamento Baseado em Eventos no Laravel

Seus controllers do Laravel frequentemente se tornam depósitos de lógica de negócio.

Você começa com um fluxo de registro simples. Logo, você adiciona notificações por e-mail, alertas no Slack, logs de auditoria e chamadas de API em um único método. Isso cria um controller inchado.

Controllers inchados tornam seu código frágil. Eles são difíceis de testar. Eles violam o Princípio de Responsabilidade Única (Single Responsibility Principle).

Você não precisa de ferramentas complexas como o RabbitMQ para resolver isso. O Laravel possui um sistema de eventos integrado que atende à maioria das necessidades.

O problema com o acoplamento forte: Se uma API de newsletter estiver lenta, o registro do usuário também ficará lento. Se um serviço de e-mail falhar, toda a requisição falhará.

A solução: Arquitetura Baseada em Eventos.

Eventos atuam como uma camada intermediária. Seu controller anuncia uma ação. Os listeners reagem a essa ação de forma independente.

Um controller enxuto se parece com isto:

public function register(RegisterRequest $request)
{
    $user = User::create($request->validated());

    UserRegistered::dispatch($user);

    return response()->json(['message' => 'Success'], 201);
}

O controller agora lida apenas com a persistência de dados. Ele não se preocupa com efeitos colaterais.

Você obtém três grandes benefícios:

  • Performance: Os usuários recebem uma resposta imediatamente. Tarefas pesadas são executadas em segundo plano usando a interface ShouldQueue.
  • Resiliência: Se um serviço estiver fora do ar, o listener pode tentar a tarefa novamente sem interromper a aplicação principal.
  • Extensibilidade: Você pode adicionar novos recursos, como notificações push, adicionando um novo listener. Você não precisa tocar no controller original.

Melhores práticas a seguir:

  • Foque em efeitos colaterais: Use eventos para pós-processamento. Não os utilize para a lógica principal que deve ocorrer instantaneamente.
  • Use nomes descritivos: Use nomes no passado, como OrderPlaced ou UserRegistered. Isso indica que a ação já ocorreu.
  • Evite a abstração excessiva: Se um trecho de código é simples e usado em apenas um lugar, uma chamada de função é melhor do que um evento.

Use Eloquent Observers para mudanças no banco de dados. Use Events para ações de negócio.

Refatorar para eventos é uma questão de durabilidade. Isso torna seu código mais fácil de depurar e mais rápido de testar.

Escolha um efeito colateral barulhento em seu controller e mova-o para um listener hoje mesmo.

Experimente este exemplo no sandbox: https://onlinephp.io/c/1f7b2

Além dos Controllers Gordos: Dominando o Desacoplamento Orientado a Eventos no Laravel

Você já se viu com um controller que parece um canivete suíço? Aquele que faz tudo: valida dados, salva no banco de dados, envia e-mails, dispara notificações e talvez até chame uma API externa.

Embora pareça prático no início, esse padrão — conhecido como "Fat Controller" (Controller Gordo) — é um sinal de alerta para a manutenção e escalabilidade do seu código.

Neste artigo, vamos explorar como podemos resolver esse problema usando o poder dos Eventos e Listeners no Laravel para alcançar um código mais limpo, desacoplado e fácil de testar.

O Problema: Controllers Gordos

Um "Fat Controller" ocorre quando a lógica de negócio começa a vazar para a camada de transporte (o Controller). O papel de um controller deve ser simples: receber uma requisição, delegar o trabalho e retornar uma resposta.

Vamos ver um exemplo de um controller que está fazendo coisas demais:

public function store(Request $request)
{
    // 1. Validação
    $validated = $request->validate([
        'product_id' => 'required|exists:products,id',
        'quantity' => 'required|integer|min:1',
    ]);

    // 2. Lógica de Negócio (Criação do Pedido)
    $order = Order::create([
        'user_id' => auth()->id(),
        'product_id' => $validated['product_id'],
        'quantity' => $validated['quantity'],
        'total_price' => Product::find($validated['product_id'])->price * $validated['quantity'],
    ]);

    // 3. Envio de E-mail
    Mail::to(auth()->user())->send(new OrderConfirmed($order));

    // 4. Notificação Push
    auth()->user()->notify(new OrderPlacedNotification($order));

    // 5. Atualização de Estoque
    $product = Product::find($validated['product_id']);
    $product->decrement('stock', $validated['quantity']);

    return response()->json($order, 201);
}

Por que isso é ruim?

  1. Violação do Princípio de Responsabilidade Única (SRP): O controller sabe demais sobre o processo de pedido.
  2. Dificuldade de Teste: Para testar a criação do pedido, você acaba disparando e-mails e notificações.
  3. Baixa Escalabilidade: Se você precisar adicionar uma nova ação (como enviar dados para um sistema de CRM), terá que modificar este controller novamente.
  4. Acoplamento Forte: O controller está fortemente ligado a várias classes de serviço (Mail, Notification, Product, etc.).

A Solução: Eventos e Listeners

O Laravel oferece um sistema de eventos robusto que permite desacoplar essas ações. Em vez de o controller executar cada tarefa, ele apenas anuncia que algo aconteceu: "Um pedido foi realizado!".

O que acontece depois que o evento é disparado é responsabilidade de outros componentes chamados Listeners.

Passo 1: Criar o Evento e o Listener

Primeiro, vamos criar o evento OrderPlaced e o listener SendOrderConfirmationEmail usando o Artisan:

php artisan make:event OrderPlaced
php artisan make:listener SendOrderConfirmationEmail --event=OrderPlaced

Passo 2: Definir o Evento

O evento OrderPlaced servirá como um contêiner de dados para o pedido que acabou de ser criado.

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderPlaced
{
    use Dispatchable, SerializesModels;

    public $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }
}

Passo 3: Definir o Listener

Agora, movemos a lógica de envio de e-mail para o listener:

namespace App\Listeners;

use App\Events\OrderPlaced;
use App\Mail\OrderConfirmed;
use Illuminate\Support\Facades\Mail;

class SendOrderConfirmationEmail
{
    public function handle(OrderPlaced $event)
    {
        Mail::to($event->order->user)->send(new OrderConfirmed($event->order));
    }
}

Passo 4: Refatorar o Controller

Agora, veja como o nosso controller fica muito mais limpo e focado:

public function store(Request $request)
{
    $validated = $request->validate([
        'product_id' => 'required|exists:products,id',
        'quantity' => 'required|integer|min:1',
    ]);

    // Delegamos a lógica de criação para um Service ou Model (opcional, mas recomendado)
    $order = OrderService::createOrder(auth()->id(), $validated);

    // Disparamos o evento!
    OrderPlaced::dispatch($order);

    return response()->json($order, 201);
}

Benefícios do Desacoplamento

Ao utilizar eventos, você ganha vários benefícios imediatos:

  • Código Limpo e Legível: O controller agora diz apenas o que aconteceu, não como cada detalhe deve ser processado.
  • Princípio de Responsabilidade Única (SRP): Cada classe tem apenas um motivo para mudar. O listener cuida do e-mail, outro listener pode cuidar do estoque, e assim por diante.
  • Facilidade de Extensão: Quer adicionar uma integração com o Slack para avisar a equipe de vendas? Basta criar um novo listener e registrá-lo. Você não toca no código do controller ou do evento.
  • Melhor Performance (Queues): Você pode implementar os listeners para rodarem em segundo plano (background) usando as Queues do Laravel, tornando a resposta para o usuário muito mais rápida.

Conclusão

Dominar o uso de eventos e listeners é um divisor de águas no desenvolvimento com Laravel. Ao sair do modelo de "Fat Controllers" e abraçar o desacoplamento orientado a eventos, você constrói aplicações mais robustas, fáceis de manter e prontas para crescer.

Na próxima vez que você sentir que seu controller está ficando grande demais, pare e pergunte-se: "Eu poderia transformar isso em um evento?"