Backend

laravel-queue

Creates queued jobs, events, listeners, notifications

Overview

The laravel-queue agent creates background job infrastructure. It generates queued jobs with proper retry logic, event-driven architectures with listeners, rate-limited processing, and batch job handling.

Responsibilities

  • Job Creation - Queued jobs with middleware, retries, and timeouts
  • Event/Listener - Event-driven architecture with proper decoupling
  • Notifications - Multi-channel notifications (mail, SMS, Slack)
  • Rate Limiting - Job throttling with Redis locks
  • Job Batching - Process related jobs together
  • Failure Handling - Retry logic and dead letter queues

What It Creates

Component Location Purpose
Jobs app/Jobs/ Background task processing
Events app/Events/ Domain events
Listeners app/Listeners/ Event handlers
Notifications app/Notifications/ User notifications

Generated Job Example

<?php

namespace App\Jobs;

use App\Models\Order;
use App\Services\PaymentGateway;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Queue\Middleware\WithoutOverlapping;

class ProcessPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public array $backoff = [60, 300, 900]; // 1min, 5min, 15min
    public int $timeout = 120;
    public int $maxExceptions = 3;

    public function __construct(
        public Order $order
    ) {}

    public function middleware(): array
    {
        return [
            new RateLimited('payments'),
            new WithoutOverlapping($this->order->id),
        ];
    }

    public function handle(PaymentGateway $gateway): void
    {
        $result = $gateway->charge($this->order);

        if ($result->failed()) {
            $this->fail($result->error());
        }

        $this->order->update(['status' => 'paid']);
    }

    public function failed(\Throwable $exception): void
    {
        $this->order->update(['status' => 'payment_failed']);

        // Notify admin
        \Notification::route('slack', config('services.slack.webhook'))
            ->notify(new PaymentFailedNotification($this->order, $exception));
    }
}

Event/Listener Pattern

// app/Events/OrderPlaced.php
class OrderPlaced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order
    ) {}
}

// app/Listeners/SendOrderConfirmation.php
class SendOrderConfirmation implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        $event->order->user->notify(
            new OrderConfirmationNotification($event->order)
        );
    }
}

// app/Listeners/UpdateInventory.php
class UpdateInventory implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        foreach ($event->order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }
    }
}

// EventServiceProvider
protected $listen = [
    OrderPlaced::class => [
        SendOrderConfirmation::class,
        UpdateInventory::class,
        NotifyWarehouse::class,
    ],
];

Job Batching

use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

$batch = Bus::batch([
    new ProcessOrder($order1),
    new ProcessOrder($order2),
    new ProcessOrder($order3),
])
->then(function (Batch $batch) {
    // All jobs completed successfully
    Log::info('Batch completed: ' . $batch->id);
})
->catch(function (Batch $batch, \Throwable $e) {
    // First batch job failure
    Log::error('Batch failed: ' . $e->getMessage());
})
->finally(function (Batch $batch) {
    // Batch finished (success or failure)
})
->allowFailures()
->dispatch();

Rate Limiting Configuration

// In AppServiceProvider
RateLimiter::for('payments', function (object $job) {
    return Limit::perMinute(100);
});

// Per-user rate limiting
RateLimiter::for('emails', function (object $job) {
    return Limit::perMinute(10)->by($job->user->id);
});

// With backoff
RateLimiter::for('api-calls', function (object $job) {
    return Limit::perMinute(60)
        ->by($job->apiKey)
        ->response(fn ($job, $headers) =>
            $job->release($headers['Retry-After'] ?? 60));
});

Invoked By Commands

Package Integration

Integrates with queue management packages:

  • laravel/horizon - Redis queue monitoring dashboard
  • spatie/laravel-failed-job-monitor - Failed job notifications
  • spatie/laravel-schedule-monitor - Schedule monitoring

Guardrails

The queue agent follows strict rules:

  • ALWAYS use SerializesModels trait for model properties
  • ALWAYS implement failed() method for error handling
  • ALWAYS set appropriate timeouts and retry limits
  • NEVER store large data in job constructors (use file paths)
  • NEVER forget to restart workers after deployment

See Also