Llamadas Directas vs Eventos — Laravel - Greetik Soluciones - Consultoría TIC Extremadura
2026-03-23 14:48:13Greetik Soluciones SL

Llamadas Directas vs Eventos — Laravel

Laravel · Arquitectura

Llamadas Directas vs Eventos

Una guía práctica sobre cuándo usar cada enfoque y por qué

1. El debate: ¿llamada directa o evento?

Cuando ocurre algo importante en tu aplicación (un usuario se registra, se procesa un pago, se crea un pedido), tienes que decidir cómo reaccionar. Hay dos caminos:

Llamada directaEvento
¿Qué es?Llamas funciones/métodos explícitamenteEmites una señal; otros escuchan
ControlTotal y explícitoDesacoplado, implícito
ComplejidadBajaMedia
EscalabilidadSe complica con el tiempoEscala bien
Ninguno es universalmente mejor. La clave está en entender cuándo cada uno tiene sentido.

2. Llamada directa

2.1 ¿Cómo funciona?

El código que detecta el evento llama directamente a las funciones que deben ejecutarse. Simple y explícito.

// OrderController.php
public function store(OrderRequest $request)
{
    $order = Order::create($request->validated());
 
    // Llamadas directas, en secuencia
    $this->sendConfirmationEmail($order);
    $this->notifyWarehouse($order);
    $this->updateStats($order);
 
    return response()->json($order, 201);
}
 
private function sendConfirmationEmail(Order $order): void
{
    Mail::to($order->customer->email)->send(new ConfirmationMail($order));
}
 
private function notifyWarehouse(Order $order): void
{
    WarehouseService::notify($order);
}

2.2 Ventajas

  • Fácil de leer: el flujo está todo en un lugar
  • Ideal para prototipos o lógica simple con 1-2 pasos
  • Sin configuración adicional: no hay que crear clases extra
  • El IDE puede trazar la ejecución sin ambigüedad

2.3 El problema: el controller crece

Tres meses después, el negocio pide más cosas al crear un pedido:

// El mismo método, 3 meses después...
public function store(OrderRequest $request)
{
    $order = Order::create($request->validated());
 
    $this->sendConfirmationEmail($order);   // original
    $this->notifyWarehouse($order);          // original
    $this->updateStats($order);              // original
    $this->addLoyaltyPoints($order);         // nuevo - marketing
    $this->syncWithERP($order);              // nuevo - contabilidad
    $this->alertFraudSystem($order);         // nuevo - seguridad
    $this->createShipmentLabel($order);      // nuevo - logística
 
    return response()->json($order, 201);
}
El problema del acoplamiento: El controller ahora sabe demasiado. Mezcla responsabilidades de marketing, contabilidad, seguridad y logística. Para añadir algo nuevo, tienes que modificar código que ya funciona — y que ya tiene tests.

3. Eventos en Laravel

3.1 Las tres piezas

Event

Clase PHP simple que transporta los datos del momento.

Listener

Clase que reacciona y ejecuta una acción concreta.

EventServiceProvider

Configuración que conecta eventos con listeners.

3.2 El mismo ejemplo con eventos

Paso 1 — Definir el evento

// app/Events/OrderCreated.php
class OrderCreated
{
    public function __construct(
        public readonly Order $order
    ) {}
}

Paso 2 — Los listeners (uno por responsabilidad)

// app/Listeners/SendConfirmationEmail.php
class SendConfirmationEmail
{
    public function handle(OrderCreated $event): void
    {
        Mail::to($event->order->customer->email)
            ->send(new ConfirmationMail($event->order));
    }
}
 
// app/Listeners/NotifyWarehouse.php
class NotifyWarehouse
{
    public function handle(OrderCreated $event): void
    {
        WarehouseService::notify($event->order);
    }
}
 
// app/Listeners/AddLoyaltyPoints.php — añadido después, sin tocar nada
class AddLoyaltyPoints
{
    public function handle(OrderCreated $event): void
    {
        LoyaltyService::award($event->order->customer, 100);
    }
}

Paso 3 — Registrar en el Provider

// app/Providers/EventServiceProvider.php
protected $listen = [
    OrderCreated::class => [
        SendConfirmationEmail::class,
        NotifyWarehouse::class,
        UpdateOrderStats::class,
        AddLoyaltyPoints::class,    // nuevo: solo añadir aquí
        SyncWithERP::class,         // sin tocar el controller
        AlertFraudSystem::class,
    ],
];

Paso 4 — El controller queda limpio para siempre

// app/Http/Controllers/OrderController.php
public function store(OrderRequest $request)
{
    $order = Order::create($request->validated());
 
    event(new OrderCreated($order)); // su trabajo termina aquí
 
    return response()->json($order, 201);
}
// Este método nunca necesita cambiar, sin importar cuántos listeners se añadan
La ventaja clave: El controller solo dice "esto ocurrió". No sabe ni le importa qué pasa después. Puedes añadir, quitar o modificar comportamiento sin tocar código que ya funciona ni sus tests.

4. Listeners asíncronos con ShouldQueue

Una ventaja exclusiva de los eventos: convertir cualquier listener en asíncrono es trivial.

// Solo añadir 'implements ShouldQueue'
class SendConfirmationEmail implements ShouldQueue
{
    public string $queue = 'emails';  // cola específica (opcional)
    public int $tries = 3;             // reintentos en caso de fallo
 
    public function handle(OrderCreated $event): void
    {
        // Esto se ejecuta en background, sin bloquear la respuesta HTTP
        Mail::to($event->order->customer->email)
            ->send(new ConfirmationMail($event->order));
    }
 
    public function failed(OrderCreated $event, Throwable $exception): void
    {
        // Se llama si los 3 intentos fallan
        Log::error('Email fallido para orden: ' . $event->order->id);
    }
}
Aislamiento de fallos: Con ShouldQueue, si el listener de email falla, el de warehouse y el de loyalty points se ejecutan igualmente. Con llamadas directas, una excepción en el email cortaría toda la cadena.

5. Testing

Llamada directa — difícil de aislar
// Tienes que mockear todos los servicios
public function test_order_creation()
{
    Mail::fake();
    $this->mock(
        WarehouseService::class,
        fn($m) => $m->shouldReceive('notify')
    );
    $this->mock(
        LoyaltyService::class,
        fn($m) => $m->shouldReceive('award')
    );
    // ...un mock por cada llamada directa
 
    $this->post('/orders', $data)
         ->assertStatus(201);
}
Con eventos — limpio y aislado
// Solo verificas que el evento se disparó
public function test_order_creation()
{
    Event::fake();
 
    $this->post('/orders', $data)
         ->assertStatus(201);
 
    Event::assertDispatched(OrderCreated::class);
}
 
// Cada listener tiene su propio test:
public function test_sends_email()
{
    Mail::fake();
    $order = Order::factory()->create();
 
    (new SendConfirmationEmail)
        ->handle(new OrderCreated($order));
 
    Mail::assertSent(ConfirmationMail::class);
}

6. ¿Cuándo usar cada enfoque?

SituaciónLlamada directaEventos
Lógica simple, 1-2 pasos✅ Perfecto❌ Innecesario
Prototipo rápido✅ Más rápido❌ Overkill
Múltiples reacciones a una acción⚠ Se complica✅ Ideal
Necesitas asincronía / colas❌ Requiere refactor✅ ShouldQueue trivial
Código de equipo grande⚠ Difícil de mantener✅ Responsabilidades claras
Tests unitarios independientes⚠ Muchos mocks✅ Fácil de aislar
Añadir comportamiento sin riesgos❌ Tocas código existente✅ Solo nuevo listener

7. Regla práctica

Empieza con llamada directa

Si tienes 1-2 cosas que hacer, una llamada directa es perfecta. No añadas complejidad innecesaria.

Migra a eventos cuando...
  • Haya 3 o más reacciones
  • Necesites asincronía
  • El equipo crezca
  • Añadir algo nuevo implique modificar código que ya funciona
Los eventos no son una solución a todo, son una herramienta para gestionar complejidad cuando la complejidad ya existe. Usarlos prematuramente añade indirección sin beneficio real.

Guía de arquitectura Laravel · Llamadas Directas vs Eventos