Async Processing
Execute handlers asynchronously via queues
Overview
Handlers can be executed asynchronously using Laravel’s queue system. This is useful for time-consuming operations that don’t need to complete before returning a response.
Using the Async Attribute
use Esegments\LaravelExtensions\Attributes\Async;
use Esegments\LaravelExtensions\Attributes\ExtensionHandler;
use Esegments\LaravelExtensions\Contracts\AsyncHandlerContract;
#[ExtensionHandler(OrderPlaced::class)]
#[Async(
queue: 'notifications',
delay: 0,
retries: 3
)]
class SendOrderNotification implements AsyncHandlerContract
{
public function handle(ExtensionPointContract $extension): void
{
// This runs in the background
$extension->order->customer->notify(
new OrderConfirmationNotification($extension->order)
);
}
}
Async Attribute Options
#[Async(
queue: 'high-priority', // Queue name
delay: 60, // Delay in seconds
retries: 3, // Max attempts
backoff: 'exponential', // Backoff strategy
backoffSeconds: 10, // Initial backoff
timeout: 120, // Job timeout in seconds
onFailure: NotifyAdmin::class,// Failure callback
onRetry: LogRetry::class, // Retry callback
uniqueJob: false, // Prevent duplicates
uniqueLockTimeout: 3600, // Unique lock duration
)]
Configuration
Default async settings in config/extensions.php:
'async' => [
'default_queue' => 'default',
'tries' => 3,
'backoff' => 10,
'backoff_strategy' => 'exponential',
],
Backoff Strategies
Linear
Wait increases by fixed amount: 10s, 20s, 30s…
#[Async(backoff: 'linear', backoffSeconds: 10)]
Exponential
Wait doubles each time: 10s, 20s, 40s, 80s…
#[Async(backoff: 'exponential', backoffSeconds: 10)]
Fixed
Same wait each time: 10s, 10s, 10s…
#[Async(backoff: 'fixed', backoffSeconds: 10)]
Custom Array
Specific delays for each retry:
#[Async(backoffSeconds: [10, 30, 60, 300])]
Failure Handling
onFailure Callback
#[Async(onFailure: NotifyOnFailure::class)]
class SendEmailHandler implements AsyncHandlerContract
{
// ...
}
class NotifyOnFailure
{
public function __invoke(Throwable $exception, ExtensionPointContract $extension): void
{
Log::error('Email handler failed', [
'exception' => $exception->getMessage(),
'extension' => get_class($extension),
]);
// Notify operations team
Slack::send("Handler failed: " . $exception->getMessage());
}
}
onRetry Callback
#[Async(onRetry: LogRetryAttempt::class)]
class ProcessPaymentHandler implements AsyncHandlerContract
{
// ...
}
class LogRetryAttempt
{
public function __invoke(int $attempt, Throwable $exception): void
{
Log::warning("Retry attempt {$attempt}", [
'error' => $exception->getMessage(),
]);
}
}
Unique Jobs
Prevent duplicate jobs for the same extension:
#[Async(
uniqueJob: true,
uniqueLockTimeout: 3600 // Lock for 1 hour
)]
class ImportProductsHandler implements AsyncHandlerContract
{
// Only one import job per product catalog at a time
}
Programmatic Async
Register async handlers programmatically:
Extensions::registerAsync(
OrderPlaced::class,
SendEmailHandler::class,
queue: 'emails',
delay: 30,
retries: 5
);
Mixing Sync and Async
You can have both sync and async handlers:
// Sync - runs immediately
Extensions::register(OrderPlaced::class, UpdateInventory::class, priority: 10);
Extensions::register(OrderPlaced::class, ProcessPayment::class, priority: 20);
// Async - queued for background
Extensions::registerAsync(OrderPlaced::class, SendConfirmation::class);
Extensions::registerAsync(OrderPlaced::class, GenerateInvoice::class);
Sync handlers execute first, then async handlers are queued.
Monitoring Async Jobs
Use Laravel Horizon or similar tools to monitor:
php artisan horizon
Or check queue status:
php artisan queue:work --queue=notifications
Best Practices
1. Serialize Extension Points Carefully
Extension points are serialized for the queue. Ensure models use SerializesModels:
class OrderPlaced implements ExtensionPointContract
{
use SerializesModels;
public function __construct(
public readonly Order $order
) {}
}
2. Use Appropriate Queues
// Time-critical
#[Async(queue: 'high')]
class SendPaymentReceipt { }
// Can wait
#[Async(queue: 'low')]
class GenerateReports { }
// Resource-intensive
#[Async(queue: 'heavy')]
class ProcessImages { }
3. Set Reasonable Timeouts
#[Async(timeout: 30)] // Quick tasks
#[Async(timeout: 300)] // Medium tasks
#[Async(timeout: 3600)] // Long-running tasks