Pub/Sub in Laravel - An In-depth Understanding

October 20, 2022
Written by
Kenneth Ekandem
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Pub/Sub in Laravel -  An In-depth Understanding

What is Pub/Sub?

Pub/Sub is a message-driven software design pattern that facilitates communication between complex system components through an efficient communication mechanism that utilises message passing. In a typical pub/sub system, there is the producer (publisher) who creates a message to be sent out to Subscribers.

The Producers do not explicitly fashion the messages for specific consumers, but instead, messages in the system are grouped into categories and the consumers indicate interest in a specific group of messages without caring about the creators of the messages as seen in the image below.

A simplistic pub/sub flow diagram

In a typical distributed real-time system setting where each component of the system performs a specific task without knowing about other parts of the system, pub/sub comes in handy because it decreases communication complexity for the parties involved. A component generates a message at the end of its task and passes it to the appropriate channel. The next component can receive the message and Initiate the next needed task and so on.

Prerequisites

To follow along with this tutorial, you will need the following:

So what will this guide cover?

In this guide, we will cover the following things in great detail

  1. How pub/sub works
  2. Importance of pub/sub in real-time systems
  3. Some pitfalls of pub/sub in real-time systems
  4. Frameworks that implement pub/sub
  5. How to implement pub/sub in Laravel

How pub/sub works

The pub/sub-system consists of four essential components which play unique roles in how the system works. Below are the roles of each component.

Publishers: Publishers, also called producers, are any part of the system that produces data. The publishers create and propagate messages through a channel to the interested receivers without any knowledge of the receiving party. This implies that the message they send is not fashioned specifically for a specific receiver since they do possess any knowledge of the interested subscribers or whether any subscriber is interested.

Subscribers: Subscribers are any part of the system that consumes data. They retrieve messages when available and perform operations based on the information. In a typical pub/sub system, a single receiver can subscribe to many channels.

Channels: The channel is the data stream through which data is transmitted from the publishers to the subscribers.

Messages: The messages are data being sent through the channels. The message sometimes comprises the header(metadata) and the body of the payload.

In the pub/sub system, a publisher typically publishes a message to a channel and the subscribers of the channel receive the message and perform the needed operation(s) on them. But the unique thing about the pub/sub system is that the identities of the parties involved are not essential and therefore can be left out.

Also, the publisher of the system can also serve as the subscriber and this makes the whole system much more flexible.

The channels on the other hand can be viewed as categories or a stack of similar items which in this case would imply that message propagation is the process of pushing new or similar item(s) to the stack. Going with the stack analogy, this implies that messages sent through the channel are always guaranteed to arrive in the same order.

It is worth noting that in order for a subscriber to receive a message they must have subscribed to the channels before the message is sent. This is in-line with all message-driven designs, a subscription has to be made before the message is received.

Pub/sub frameworks 

The following are several well-known frameworks that support pub/sub:

Redis: Redis is an in-memory data structure stored as a database, cache, message broker, and streaming engine but it also comes strapped with a pub/sub system of its own.

Apache Kafka: is a popular and robust messaging tool that has the usual Pub/Sub features. Apache Kafka is also designed to include a message log. Subscribers can step back and forth in time to ‘replay’ messages.

RabbitMQ: is a message queue that does not really follow the pub/sub pattern. However, it can be configured for direct message exchange between two or more components of the system.

Importance of pub/sub in real-time messaging

  • Ease of communication: With the introduction of pub/sub into a real-time system, all components of the system benefit from the ease of communication it brings as the components can communicate in an asynchronous way.
  • Loose Coupling Between System Components: For a real-time system where each part performs a special task of its own, pub/sub fit in well and benefit from loose-coupling. This further strengthens the system as each part can be tested independently without depending on the other components.
  • Frugality With Resource Consumption: With pub/sub, components of the system do not need to poll each other for updates but can instead rely on the channels for updates. This reduces the consumption of network bandwidth as well as other resources involved.
  • Guaranteed Consistent Reliability: Since communication is facilitated by message passing, modification of one component of the system can be done without affecting other parts of the system as long as the message they publish is valid.
  • Scalability: The introduction of pub/subs into a system allows the system to be scaled without the fear of breaking functionality because both the subscriber and consumers are separate entities and function independently.

How to implement pub/sub in Laravel

Now, you'll learn how to set up Redis Pub/Sub in a Laravel project.

Step 1: Creating A Project

To create a new Laravel project in a new directory named pub-sub, run the command below.

composer create-project laravel/laravel pub-sub

Change into the folder and start the application on your local machine, by running the commands below.

cd pub-sub
php artisan serve

After running the commands above, your app should be listening on localhost on port 8000. Navigate to http://localhost:8000 to view your Laravel app.

Step 2: Installing Redis

Next, in a new terminal session, run the code below to add the application's dependencies: predis and the Illuminate Redis package.

composer require "illuminate/redis:*" predis/predis 

After the installation, in .env, add  REDIS_CLIENT=predis above REDIS_HOST=127.0.0.1. Then, if you need to,  update the value of REDIS_PASSWORD with your Redis password .

Next, test if the Redis installation was successful. To do that, update the default route's handler in routes/web.php to match the code below.

use Illuminate\Support\Facades\Redis;
Route::get('/', function () {
    print(Redis::get('hello') ?: 'Key not found');
});

Then, reload your browser. You shouldn’t get an error if installation of the Redis extension was successful. And, as there is no key in Redis named hello, you should see the string "Key not found".

Firefox showing that the Redis key was not found.

Implementing Redis Pub/Sub in Laravel

To demonstrate pub/sub in Laravel, we are going to create a small blog that covers a range of topics. Users can subscribe to one or more topics of their choice and will be notified when posts on such topics are made.

To start,  generate a blog post controller by running the command below.

php artisan make:controller BlogController

Then, add the method below to handle the blog creation request.

public function create(Request $request)
{
    $blog = $request->input('blog');
    Redis::publish('create:blog', json_encode($blog));
    return $blog;
}

After that, add the use statement below.

use Illuminate\Support\Facades\Redis;

This method grabs the blog data from the request body, publishes the data to our brand channel with Redis::publish, and then returns the same value.

Whenever we call the Redis::publish method, Redis first checks if the channel was created and if not it creates one and publishes the message to the queue. The same applies to the Redis::subscribe method you’ll see later.

In routes/api.php, add a new route pointing to the controller create method, by adding the code below.

Route::post('/create', [BlogController::class, 'create']);

Then, add the following use statement at the top of the file.

use App\Http\Controllers\BlogController;

When you make a POST request to http://localhost:8000/create, the data you send in the request body will be sent back as a response, and a new message will be sent to the create:blog channel.  

So far, all you have is a publisher which feeds the create:blog channel, whenever the /create endpoint is requested. Now let’s create a subscriber.

For the subscriber, you’ll be using a command since the subscription is a continuous process and will not stop running until you stop it, or an error occurs.

To start it, run the command below to create a new command.

php artisan make:command NotifierCommand

If you check the app\Console\Commands folder, you will find a new file named  NotifierCommand.php. Update it to match the code below.

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;

class NotifierCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'notifier';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Notifies users on new post';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        echo 'start listening for blog creation' . PHP_EOL;
        Redis::subscribe('create:blog', function ($blog) {
            echo 'message received!' . PHP_EOL;
            $blog = json_decode($blog);
            $users = [
                [
                    "name" => "John Doe",
                    "email" => "jon@gmail.com",
                    "topics" => ['sports', 'food']
                ],
                [
                    "name" => "Jane Doe",
                    "email" => "jane@gmail.com",
                    "topics" => ['sports', 'fashion']
                ]
            ];
            foreach ($users as $user) {
                foreach ($user['topics'] as $topic) {
                    if ($blog->topic === $topic) {
                        echo 'New blog on "' . $topic . '" for "' . $user['name'] . '" with title => "' . $blog->title . PHP_EOL;
                    }
                }
            }
        });
    }
}

The code above asks Redis to listen to the create:blogchannel and execute the provided function whenever a message is available using the Redis:subscribe method. Every time a new message is received, it will inspect the list of users and print out users with topics matching the blog’s topic.

Now, start the notifier command by running the command below.

php artisan notifier

To test it, run the following command in the terminal.

curl -X POST http://localhost:8000/api/create\
   -H 'Content-Type: application/json'\
   -H 'accept: application/json'\
   -d '{"blog": { "title": "Manchester City Wins again with a stellar performance", "topic": "sports", "body": "Credibly enhance empowered benefits for enterprise-wide schemas. Compellingly foster cooperative ideas and focused communities. Rapidiously exploit e-business strategic theme areas via prospective web-readiness. Intrinsically streamline"}}'

Now, check the command line interface where you ran the notifier command. You should see something like the following:

start listening for blog creation
message received!
New blog on "sports" for "John Doe" with title => "Manchester City Wins again with a stellar performance
New blog on "sports" for "Jane Doe" with title => "Manchester City Wins again with a stellar performance

If you change the topic in the request, you will notice that the logs will change accordingly. This is because it matches the new blog’s topic with the users’ topics. Congratulations on implementing a pub/sub system in Laravel.

Pitfalls of pub/sub in real-time systems

It is worth noting that while introducing pub/sub into your system might bring immense benefits, it also has several pitfalls.

One of the most prominent pitfalls of pub/sub is failed message delivery. Once a message is sent, there is no guarantee that the message was received or acted upon.

You can salvage this by introducing a channel where the producers can listen for replies from the consumers, but that will rob the system of its loose coupling. Perhaps a better solution might be switching to a message queue and ditching pub/sub entirely.

Real-world applications of Pub/sub

Armed with an understanding of how works, you are only limited by your imagination when it comes to what you can do with pub/sub, but here are some cool applications of pub/sub in real-world contexts which are worth mentioning:

  • Streaming data between two devices in IoT
  • An event bus to facilitate communication between distributed enterprise systems.

In this article, you learned about how pub/sub works and its importance in real-time systems. You also learned about some of the prominent pitfalls of pub/sub as well as a potential solution.

I hope this helped broaden your knowledge of pub/sub system design and also informed your choice on when to pull pub/sub into your application.  Thank you for reading.

Kenneth Ekandem is a full-stack developer from Nigeria currently in the blockchain space, but interested in learning everything computer science has to offer. He'd love to go to space one day and also own his own vlogging channel to teach the next generation of programmers.