DEV Community

Aleson França
Aleson França

Posted on

Using RabbitMQ with Queues in Laravel

RabbitMQ is a widely used message broker for asynchronous task processing. In Laravel, we can integrate it as a queue driver to improve application performance and scalability.

Setting Up RabbitMQ in Laravel

Installing the Package

Laravel does not have native support for RabbitMQ, so we use a third-party package like vladimir-yuldashev/laravel-queue-rabbitmq.

composer require vladimir-yuldashev/laravel-queue-rabbitmq


Configuring config/queue.php

Add the RabbitMQ configuration:

<?php

'connections' => [
    'rabbitmq' => [
        'driver' => 'rabbitmq',
        'hosts' => [
            [
                'host' => env('RABBITMQ_HOST', '127.0.0.1'),
                'port' => env('RABBITMQ_PORT', 5672),
                'user' => env('RABBITMQ_USER', 'guest'),
                'password' => env('RABBITMQ_PASSWORD', 'guest'),
                'vhost' => env('RABBITMQ_VHOST', '/'),
            ],
        ],
        'queue' => env('RABBITMQ_QUEUE', 'default'),
        'options' => [
            'exchange' => [
                'name' => env('RABBITMQ_EXCHANGE_NAME', 'default'),
                'type' => env('RABBITMQ_EXCHANGE_TYPE', 'direct'),
            ],
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

Setting Env Vars

Add to .env:

QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_VHOST=/
RABBITMQ_QUEUE=default
Enter fullscreen mode Exit fullscreen mode

Creating and Dispatching Jobs

php artisan make:job ProcessPayment

Edit app/Jobs/ProcessPayment.php

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique; 
use Illuminate\Contracts\Queue\ShouldBeQueued;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;
use App\Models\Inventory;
use App\Services\PaymentService;
use App\Exceptions\PaymentFailedException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

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

    public $tries = 3; // The maximum number of times this job will be attempted
    public $backoff = [10, 30, 60]; // The delay in seconds before retrying the job after a failure, increasing with each attempt

    protected $paymentService;

    // Constructor to inject the PaymentService dependency
    public function __construct(PaymentService $paymentService)
    {
        $this->paymentService = $paymentService;
    }

    // This method contains the main logic to process the payment
    public function handle()
    {
        try {
            // Step 1: Process the payment using the injected PaymentService
            $paymentSuccess = $this->paymentService->process();

            // If the payment was not successful, throw a specific exception
            if (!$paymentSuccess) {
                throw new PaymentFailedException('Payment processing failed.');
            }

            // Step 2 & 3: Create the order and deduct inventory within a database transaction to ensure data consistency
            DB::transaction(function () {
                // Check if an order with the same criteria already exists to ensure idempotency
                $order = Order::firstOrCreate([
                    'user_id' => 1,
                    // Add other unique criteria for identifying existing orders
                ], [
                    'status' => 'paid',
                    'total' => 100.00,
                ]);

                // Deduct the item quantity from the inventory (consider using optimistic/pessimistic locking for concurrency)
                Inventory::where('product_id', 1)->decrement('quantity', 1);
            });

        } catch (PaymentFailedException $e) {
            // Specific handling for payment failures
            Log::warning('Payment failure while processing order: ' . $e->getMessage());
            throw $e; // Re-throw the exception to trigger the retry mechanism
        } catch (Exception $e) {
            // Handling for unexpected errors
            Log::error('Unexpected error while processing order: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
            throw $e; // Re-throw the exception to trigger the retry mechanism
        }
    }

    // This method is executed if the job fails after all retry attempts
    public function failed(Exception $exception)
    {
        // Logic to be executed when the job completely fails
        Log::critical('Critical failure while processing order after multiple retries: ' . $exception->getMessage(), ['trace' => $exception->getTraceAsString()]);
        // Consider sending notifications to support teams or implementing other failure handling strategies
    }
}
Enter fullscreen mode Exit fullscreen mode

Dispatching a Job

use App\Jobs\ProcessPayment;

ProcessPayment::dispatch();
Enter fullscreen mode Exit fullscreen mode

Running the worker

php artisan queue:work rabbitmq


Conclusion

By integrating RabbitMQ with Laravel queues, you can significantly enhance your application's performance by offloading time-consuming tasks to background processing. This results in a more responsive user experience and improved system scalability.

With features like automatic retries and message durability, RabbitMQ ensures that tasks are processed reliably even in case of failures. Using Laravel's queue system with RabbitMQ allows you to build robust and efficient workflows, making it an excellent choice for large-scale applications requiring asynchronous processing.

For more details, refer to the official documentation:

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (4)

Collapse
 
xwero profile image
david duymelinck

I checked the package and it hasn't been updated since may 2024. In that time a new major version (4) came out with an update of the protocol (1). The version 3 releases are unsupported.
This makes me a bit wary about using the package.

Collapse
 
aleson-franca profile image
Aleson França

Thanks David, I recommend using any package with caution.
Another package I use is php-amqplib/php-amqplib you can analyze it is quite large.

Collapse
 
qasim_naseer_eecc5f02605b profile image
Qasim Naseer

Hello Aleson , thank you for sharing this , have you thought of integrating with Kafka and would you recommend rabbitmq or kafka for someone who is using horizon with redis ? to scale and ensure no message goes unaddressed. Is rabbitmq for large scale application or one should look for kafka ?

Collapse
 
aleson-franca profile image
Aleson França

Depending on what you want, rabbit and kafka are totally different. Try to analyze the differences and see what you really need.

👋 Kindness is contagious

If you found this post helpful, please leave a ❤️ or a friendly comment below!

Okay