laravelqueuesjobslaravel-13

Queue::forward() en Laravel 13.26: Reencamina Colas sin Modificar Jobs

Introducción

Uno de los desafíos más comunes en aplicaciones Laravel con múltiples colas es la necesidad de reencaminar trabajos dinámicamente. Imagina que tu aplicación procesa emails, notificaciones y procesamiento de imágenes en diferentes colas, pero necesitas cambiar estrategias de enrutamiento sin tocar el código de tus jobs.

Laravel 13.26 introduce Queue::forward(), una solución elegante que permite reencaminar trabajos a diferentes colas o conexiones desde un proveedor de servicios, sin modificar absolutamente nada en tus clases de jobs.

En este artículo exploraremos cómo funciona, cuándo usarlo y patrones reales para maximizar su potencial.

¿Qué es Queue::forward()?

Queue::forward() es un método que intercepta trabajos en cola antes de que se ejecuten y los reencamina a otra cola o conexión. La magia está en que ocurre a nivel de configuración, completamente transparente para tus jobs.

// En tu ServiceProvider
use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    Queue::forward('emails', 'high-priority');
}

Este ejemplo simple redirige todos los trabajos de la cola emails a la cola high-priority sin tocar ningún job. Es configuración pura, separada de la lógica de negocio.

Casos de uso reales

1. Separar por entorno

En desarrollo, quizás quieras procesar todos los jobs sincronizadamente, pero en producción usar Redis. Queue::forward() lo permite:

// AppServiceProvider.php
public function boot(): void
{
    if (app()->isProduction()) {
        Queue::forward('emails', connection: 'redis');
        Queue::forward('notifications', connection: 'redis');
        Queue::forward('images', connection: 'redis');
    } else {
        // En desarrollo, usa 'sync' para ejecución inmediata
        Queue::forward('*', connection: 'sync');
    }
}

2. Priorizar colas dinámicamente

Supón que tienes dos trabajos que compiten por recursos. Durante horas pico, quieres priorizar emails sobre notificaciones:

use Illuminate\Support\Facades\Queue;
use Carbon\Carbon;

public function boot(): void
{
    // Durante horario de oficina, emails con máxima prioridad
    if (Carbon::now()->between('09:00', '18:00')) {
        Queue::forward('notifications', 'emails');
    }
}

3. Balanceo de carga

Distribuye trabajos entre múltiples conexiones para evitar sobrecargar una sola:

public function boot(): void
{
    // Alterna entre Redis clusters
    Queue::forward('heavy-processing', 'redis-cluster-1');
    Queue::forward('reports', 'redis-cluster-2');
    Queue::forward('backups', 'redis-cluster-3');
}

Sintaxis completa

Queue::forward() acepta varios parámetros:

Queue::forward(
    $queue,              // Cola de origen (string o array)
    $target = null,      // Cola destino (string)
    $connection = null   // Conexión destino (string)
);

Ejemplos avanzados

use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    // Una cola a otra
    Queue::forward('slow-jobs', 'fast-queue');
    
    // Una cola a otra conexión
    Queue::forward('background', connection: 'database');
    
    // Múltiples colas al mismo destino
    Queue::forward(['notifications', 'alerts'], 'urgent');
    
    // Todo lo que no sea especificado a una cola por defecto
    Queue::forward('*', 'default');
}

Casos de uso avanzados

Reencaminamiento condicional con proveedores personalizados

Si necesitas lógica más compleja, crea un proveedor personalizado:

// app/Providers/DynamicQueueRoutingProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Queue;
use App\Services\LoadBalancer;

class DynamicQueueRoutingProvider extends ServiceProvider
{
    public function boot(): void
    {
        $loadBalancer = $this->app->make(LoadBalancer::class);
        
        // Obtén la cola destino según carga actual
        $emailQueue = $loadBalancer->getLeastLoadedQueue(['redis-1', 'redis-2']);
        Queue::forward('emails', connection: $emailQueue);
        
        // Prioriza trabajos urgentes
        if ($loadBalancer->isPeakHours()) {
            Queue::forward('reports', 'low-priority');
        } else {
            Queue::forward('reports', 'normal');
        }
    }
}

Múltiples niveles de prioridad

public function boot(): void
{
    // Crítico
    Queue::forward('security-alerts', 'priority-1');
    
    // Alto
    Queue::forward(['payments', 'orders'], 'priority-2');
    
    // Normal
    Queue::forward(['emails', 'notifications'], 'priority-3');
    
    // Bajo
    Queue::forward(['reports', 'analytics'], 'priority-4');
}

Comparación con alternativas

Sin Queue::forward() (forma antigua)

// En cada job
namespace App\Jobs;

class SendEmail implements ShouldQueue
{
    // ❌ Acoplado a la cola
    public $queue = 'emails';
    
    public function handle(): void
    {
        // lógica
    }
}

// Si cambias de estrategia, modificas TODOS los jobs

Con Queue::forward() (forma nueva)

// En ServiceProvider (centralizado)
Queue::forward('emails', 'high-priority');

// Jobs sin cambios ❌ Sin acoplamiento
namespace App\Jobs;

class SendEmail implements ShouldQueue
{
    // El job no sabe ni le importa dónde se procesa
    public function handle(): void
    {
        // lógica
    }
}

Monitoreo y debugging

Aunque Queue::forward() es transparente, puedes agregar logs para entender el flujo:

public function boot(): void
{
    Queue::before(function (JobProcessing $event) {
        Log::info("Job {$event->job->getJobId()} iniciando en cola: {$event->job->getQueue()}");
    });
    
    Queue::after(function (JobProcessed $event) {
        Log::info("Job {$event->job->getJobId()} completado");
    });
    
    // Forward con logging
    Queue::forward('slow-jobs', 'optimized', function ($job) {
        Log::channel('queue-routing')->info("Reenviando {$job->resolveName()} a optimized");
    });
}

Limitaciones y consideraciones

No funciona con todas las conexiones

Queue::forward() funciona mejor con conexiones que soportan dinámicamente configuración de colas:

// ✅ Funciona bien
Queue::forward('emails', connection: 'redis');
Queue::forward('jobs', connection: 'database');

// ⚠️ Limitado (algunos drivers no lo soportan completamente)
Queue::forward('sync', connection: 'sync');

Performance

El reencaminamiento ocurre en tiempo real sin overhead significativo, pero si tienes cientos de rules, considera agruparlas:

// ❌ Innecesariamente específico
Queue::forward('email-welcome', 'high');
Queue::forward('email-reset', 'high');
Queue::forward('email-confirm', 'high');

// ✅ Mejor
Queue::forward(['email-*'], 'high');

Integración con otras features de Laravel

Con workers supervisados

// supervisor-queue-routing.conf
[program:laravel-queue-high]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/artisan queue:work --queue=high-priority

[program:laravel-queue-normal]
command=php /path/to/artisan queue:work --queue=normal

; Queue::forward() automáticamente enruta a las colas correctas

Con Laravel Horizon

Queue::forward() juega bien con Horizon, que monitorea múltiples colas:

// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => 'high-priority', // Horizon sigue los forwards
            'balance' => 'auto',
            'procs' => 10,
        ],
    ],
],

Ejemplo completo: Sistema de notificaciones multi-prioridad

// app/Providers/NotificationQueueProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Queue;
use App\Models\SystemHealth;

class NotificationQueueProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Obtén estado del sistema
        $health = SystemHealth::current();
        
        // Configura enrutamiento según salud
        if ($health->cpu_usage > 80 || $health->memory_usage > 85) {
            // Sistema bajo estrés: prioriza crítico
            Queue::forward('security', 'critical');
            Queue::forward('payments', 'critical');
            
            // Defer everything else
            Queue::forward('emails', 'deferred');
            Queue::forward('reports', 'deferred');
            Queue::forward('analytics', 'deferred');
        } else {
            // Sistema sano: distribución normal
            Queue::forward('security', 'high');
            Queue::forward('payments', 'high');
            Queue::forward('emails', 'normal');
            Queue::forward('reports', 'low');
            Queue::forward('analytics', 'low');
        }
        
        // Fallback: todo lo demás va a default
        Queue::forward('*', 'default');
    }
}

Jobs sin cambios:

namespace App\Jobs;

class SendSecurityAlert implements ShouldQueue
{
    public function __construct(
        private string $alertType,
        private array $details,
    ) {}
    
    public function handle(): void
    {
        // El job no sabe dónde se ejecuta
        // Queue::forward() lo maneja
        Log::critical("Security alert: {$this->alertType}");
    }
}

class GenerateMonthlyReport implements ShouldQueue
{
    public function handle(): void
    {
        // Automáticamente en 'low' si el sistema está estresado
        // O en 'low' en cualquier caso
    }
}

Puntos clave

  • Queue::forward() reencamina trabajos sin modificar clases de jobs
  • Centralización: toda la lógica de enrutamiento en un proveedor
  • Dinámico: funciona con lógica condicional en tiempo real
  • Múltiples niveles: soporta varios destinos y conexiones
  • Transparencia: los jobs permanecen desacoplados de la infraestructura
  • Casos de uso: priorización, balanceo de carga, separación por entorno
  • Integración: funciona con Horizon y workers supervisados
  • Sin overhead: reencaminamiento eficiente sin penalización de performance
  • Simplifica arquitectura: reemplaza configuración hardcodeada en jobs
  • Testing: facilita cambiar comportamiento sin modificar código de negocio