RabbitMQ with Laravel

Azraar Azward
8 min readFeb 12, 2022

--

This article explains the usage of a message broker and how to set up a quick example using Laravel and RabbitMQ.

What is RabbitMQ?

As per google, RabbitMQ is an open-source message broker software (also known as message queue) that implements the Advanced Message Queuing Protocol (AMQP).

in addition to AMQP, RabbitMQ supports the below protocols

  • HTTP
  • STOMP
  • MQTT

When to use a message broker?

Let’s take a coffee shop business model as a real-world example. In a coffee shop, there is usually someone at the counter taking orders. When a customer makes an order, they don’t stop making orders but keep taking orders from the new customers with a message (response) saying “Please sit down, your order will be served shortly”

Now they put the orders made by different customers on a list while continuing to take new orders, the list may consist of an order number and the items.

Eg:

1, LATTE

2, MOCHA

3, CAPPUCCINO

4, AMERICANO

5, ESPRESSO

If you had to stop taking new orders because you had to serve the first customer then there is going to be unnecessary waiting time which can impact the whole business. so now while somebody makes the coffee you can take as many orders as you like.

It’s a simple architecture as such and when you are done making a coffee you can remove it from the list, which is in message broker term known as removing from the queue. when you remove it, you also offer the coffee to customers and they’re entirely relieved. But did you notice? This whole thing was asynchronous, meaning the customer did not wait for your response as they were notified as the order is received and will be served shortly.

During this precious time the customer was able to do other tasks.

They might be checking their phone, they might be going out, etc. In technical terms we allowed the client to be happier to distribute its resources elsewhere instead of just waiting for a response.

Now imagine there is a coffee type that takes less time to prepare (AMERICANO) and there is another type that takes more time to prepare (CAPPUCCINO), so those orders can be put according to the priority and allow customers to spend the time more wisely just by using asynchronous processing.

Now, what could happen next for the coffee shop business? It can grow and start to franchise and have a bigger chain.

Let’s say something like Starbucks.

so there is Starbucks 1, Starbucks 2, Starbucks 3, etc and each serves approximately thousands of customers.

Now, let’s consider the worst day of a business where there is a power outage in a city where a Starbucks 1 branch is located. How can we serve the orders that are already taken? You can connect them to the nearest branch and get them delivered so those orders can be completed and customers are retained.

But how can you do this technically?

Well, the simple way of maintaining a list in memory won’t work because once the shop is down it loses electricity then the computer is going to shut down so you need some sort of persistence in your data.

Let’s say we try it with a database where the list gets stored in a server and load balancers are configured with 4 nodes called n1, n2, n3, n4.

What happens if a node goes down say for example n2?

We can keep track of which orders are served by which nodes in the database and try to serve through available nodes but this gets complicated when more requests and more nodes are going down also maintaining the status of orders and prioritising.

From a real-world perspective, the following issues can happen,

Wrong coffee can get delivered to the wrong customer or the same customer might receive multiple coffees or the customer might not receive a coffee at all.

This can be solved through load balancing and some sort of a heartbeat mechanism, you can notify all the failed orders to the available servers.

Now, what if you want all the features of notification, load balancing, a heartbeat, and persistence in one thing?

That would be a message queue.

So what this does is it takes tasks, persists them, assigns them to the correct server, and waits for them to complete. If it’s taking too long for the server to give an acknowledgment, it feels that server is dead and then assigns it to the next server. A message queue has multiple strategies of how this can be achieved but that’s encapsulated in the message queue for example RabbitMQ. (I will create another article later to explain the strategies and other components of RabbitMQ)

There are also other alternatives to RabbitMQ,

  • IBM MQ.
  • Apache Kafka.
  • Google Cloud Pub/Sub.
  • Amazon MQ.
  • Apache ActiveMQ.
  • KubeMQ.
  • ZeroMQ.

How to spine up a RabbitMQ instance

Include RabbitMQ section in the docker-compose file and run a docker-compose up.

version: '3'services:rabbitmq:
image: "rabbitmq:3-management"
hostname: "rabbit"
ports:
- "15672:15672"
- "5672:5672"
labels:
NAME: "rabbitmq"
volumes:
- ./rabbitmq-isolated.conf:/etc/rabbitmq/rabbitmq.config

This will launch a RabbitMQ docker container including the management website. You can log in and check via localhost:15672

Installing RabbitMQ with Laravel

Laravel doesn’t support RabbitMQ by default, but there is a package that enables it to be supported.

We will require the following package to make it work with Laravel

https://github.com/vyuldashev/laravel-queue-rabbitmq

Then to create the connection between PHP and RabbitMQ, you must install any available AMQP support package.

For that I choose https://github.com/php-enqueue/amqp-bunny

Then we need to configure the queueing system

In your app’s config/queue.php file, add the new configuration item for rabbitmq inside the connections array:

'connections' => [
'rabbitmq' => [
'driver' => 'rabbitmq',
/*
* Set to "horizon" if you wish to use Laravel Horizon.
*/
'worker' => env('RABBITMQ_WORKER', 'default'),
'dsn' => env('RABBITMQ_DSN', null),
/*
* Could be one a class that implements \Interop\Amqp\AmqpConnectionFactory for example:
* - \EnqueueAmqpExt\AmqpConnectionFactory if you install enqueue/amqp-ext
* - \EnqueueAmqpLib\AmqpConnectionFactory if you install enqueue/amqp-lib
* - \EnqueueAmqpBunny\AmqpConnectionFactory if you install enqueue/amqp-bunny
*/
'factory_class' => \Enqueue\AmqpBunny\AmqpConnectionFactory::class,
'host' => env('RABBITMQ_HOST', '127.0.0.1'),
'port' => env('RABBITMQ_PORT', 5672),
'vhost' => env('RABBITMQ_VHOST', '/'),
'login' => env('RABBITMQ_LOGIN', 'guest'),
'password' => env('RABBITMQ_PASSWORD', 'guest'),
'queue' => env('RABBITMQ_QUEUE', 'default'),
'options' => [
'exchange' => [
'name' => env('RABBITMQ_EXCHANGE_NAME'),
/*
* Determine if exchange should be created if it does not exist.
*/
'declare' => env('RABBITMQ_EXCHANGE_DECLARE', true),
/*
* Read more about possible values at https://www.rabbitmq.com/tutorials/amqp-concepts.html
*/
'type' => env('RABBITMQ_EXCHANGE_TYPE',\Interop\Amqp\AmqpTopic::TYPE_DIRECT),
'passive' => env('RABBITMQ_EXCHANGE_PASSIVE', false),
'durable' => env('RABBITMQ_EXCHANGE_DURABLE', true),
'auto_delete' => env('RABBITMQ_EXCHANGE_AUTODELETE', false),
'arguments' => env('RABBITMQ_EXCHANGE_ARGUMENTS'),
],
'queue' => [
/*
* Determine if queue should be created if it does not exist.
*/
'declare' => env('RABBITMQ_QUEUE_DECLARE', true),
/*
* Determine if queue should be binded to the exchange created.
*/
'bind' => env('RABBITMQ_QUEUE_DECLARE_BIND', true),
/*
* Read more about possible values at https://www.rabbitmq.com/tutorials/amqp-concepts.html
*/
'passive' => env('RABBITMQ_QUEUE_PASSIVE', false),
'durable' => env('RABBITMQ_QUEUE_DURABLE', true),
'exclusive' => env('RABBITMQ_QUEUE_EXCLUSIVE', false),
'auto_delete' => env('RABBITMQ_QUEUE_AUTODELETE', false),
'arguments' => env('RABBITMQ_QUEUE_ARGUMENTS'),
],
],
/** Determine the number of seconds to sleep if there's an error communicating with rabbitmq
* If set to false, it'll throw an exception rather than doing the sleep for X seconds.
*/
'sleep_on_error' => env('RABBITMQ_ERROR_SLEEP', 5),
/*
* Optional SSL params if an SSL connection is used
* Using an SSL connection will also require to configure your RabbitMQ to enable SSL. More details can be founds here: https://www.rabbitmq.com/ssl.html
*/
'ssl_params' => [
'ssl_on' => env('RABBITMQ_SSL', false),
'cafile' => env('RABBITMQ_SSL_CAFILE', null),
'local_cert' => env('RABBITMQ_SSL_LOCALCERT', null),
'local_key' => env('RABBITMQ_SSL_LOCALKEY', null),
'verify_peer' => env('RABBITMQ_SSL_VERIFY_PEER', true),
'passphrase' => env('RABBITMQ_SSL_PASSPHRASE', null),
],
],
],
...

And in your app’s .env file, set parameters like this:

QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=rabbitmq
RABBITMQ_PORT=5672
RABBITMQ_VHOST=/
RABBITMQ_LOGIN=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_QUEUE=default
RABBITMQ_EXCHANGE_NAME=default
RABBITMQ_EXCHANGE_DECLARE=
RABBITMQ_EXCHANGE_TYPE=
RABBITMQ_EXCHANGE_PASSIVE=
RABBITMQ_EXCHANGE_DURABLE=
RABBITMQ_EXCHANGE_AUTODELETE=
RABBITMQ_EXCHANGE_ARGUMENTS=default
RABBITMQ_QUEUE_DECLARE=
RABBITMQ_QUEUE_DECLARE_BIND=
RABBITMQ_QUEUE_PASSIVE=
RABBITMQ_QUEUE_DURABLE=
RABBITMQ_QUEUE_EXCLUSIVE=
RABBITMQ_QUEUE_AUTODELETE=
RABBITMQ_QUEUE_ARGUMENTS=default
RABBITMQ_ERROR_SLEEP=5
RABBITMQ_SSL=
RABBITMQ_SSL_CAFILE=
RABBITMQ_SSL_LOCALCERT=
RABBITMQ_SSL_LOCALKEY=
RABBITMQ_SSL_VERIFY_PEER=
RABBITMQ_SSL_PASSPHRASE=

Now you can create a dummy Job to see how its processed

php artisan make:job TestQueue

TestQueue.php Code

<?phpnamespace App\Jobs;use App\Models\UserInfo;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TestQueue implements ShouldQueue
{
...
private $data; /**
* Create a new job instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$data = $this->data;
UserInfo::query()->firstOrCreate(
['phone' => $data['phone']],
['name' => $data['name']]
);
}
}

Create a test method to dispatch the job

public function test(Request $request){
$data = ['name' => 'Jon Doe', 'phone' => '12345678901'];
$this->dispatch(new TestQueue($data));
}

Now you can run the job and it is using RabbitMQ now.

php artisan queue:work

or

php artisan rabbitmq:consume

The above is a simple Laravel example replacing default queue with RabbitMQ, now lets say you have a micro services architecture and you want to pass data between two services (coffee service and review service) say for example when coffee order is removed we need to pass that information to the review service.

Create a job to send coffee deletion message from product service.

php artisan make:job CoffeeDeleted

coffee-service/app/Jobs/CoffeeDeleted.php

<?php

namespace App\Jobs;

...

class CoffeeDeleted implements ShouldQueue
{
...

private $data;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
}
}

Dispatching the event along with product id

CoffeeService/app/Http/Controllers/CoffeeController.php

?php

namespace App\Http\Controllers;

use App\Models\Coffee;
use App\Jobs\CoffeeDeleted;

class CoffeeController extends Controller
{
public function destroy(Coffee $product)
{
$product->delete();

CoffeeDeleted::dispatch([
'id' => $product->id
]);
return response(null);
}
}

Create a CoffeeDeleted job in review-service to receive a message about coffee deletion.

php artisan make:job CoffeeDeleted

ReviewService/app/Jobs/CoffeeDeleted.php

<?php

namespace App\Jobs;

use App\Models\Review;
...

class CoffeeDeleted implements ShouldQueue
{
...

private $data;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if ($this->data) {
Review::where('product_id', $this->data['id'])->delete();
echo "Review has been deleted. coffee_id: ".$this->data['id'].PHP_EOL;
}
}
}

To receive the message from product to review service we can run,

php artisan queue:work

or

php artisan rabbitmq:consume

Now, you can delete the product and test whether the message is actually delivered to the review service.

Bonus

  1. You can get a free cloud RabbitMQ instance by signing up at https://www.cloudamqp.com/
  2. A free crash course on how to create a microservice based application using Laravel and RabbitMQ https://www.youtube.com/watch?v=SzsPe_QX__c&ab_channel=ScalableScripts

--

--

Azraar Azward

An eager Software Engineer always loving to reach the impossible.