Introduction to RabbitMQ and Symfony

Fabio Hiroki - Jan 2 '22 - - Dev Community

One day I was trying to learn the deep concepts of RabbitMQ, its use cases and why it's different from other message brokers. I've started by reading the cool documentation and then I was eager to try it out in a demo application.

Turns out it wasn't so easy to setup a Symfony application and connect to RabbitMQ. Google displayed different solutions and I also needed StackOverflow to install some additional dependencies.

Hopefully I could condense all the information and display it here in a simple and fun way.

What am I going to build?

A man confused
Initially I thought of creating a web application to explore the different patterns of using RabbitMQ. After struggling with the setup of RabbitMQ using a popular web framework, I decided to take a step back and simply create an endpoint that publishes a message, and a consumer that logs the content received. So let's build it!

Final code on Github:


Symfony

But why Symfony, right? It's a popular PHP framework and I really liked its software architecture and integration with RabbitMQ.

First, I installed Symfony CLI and create a a traditional web application:

symfony new --full php-symfony-rabbitmq
Enter fullscreen mode Exit fullscreen mode

My application now can be started by running the following command on the new project directory:

symfony serve
Enter fullscreen mode Exit fullscreen mode

Symfony messenger

A mailman showing a letter
Symfony Messenger is the message bus abstraction provided by a separate lib. Let's install it then!

composer require symfony/messenger
Enter fullscreen mode Exit fullscreen mode

Following the good documentation, I created a simple class to encapsulate the message to be published:

final class SampleMessage
{
    public function __construct(private string $content)
    {
    }

    public function getContent(): string
    {
        return $this->content;
    }
}
Enter fullscreen mode Exit fullscreen mode

And its respective handler:

final class SampleMessangeHandler implements MessageHandlerInterface
{
    public function __invoke(SampleMessage $message)
    {
        // magically invoked when an instance of SampleMessage is dispatched
        print_r('Handler handled the message!');
    }
}
Enter fullscreen mode Exit fullscreen mode

Now to see if everything is working so far, I added a simple endpoint to dispatch a message:

final class SampleController extends AbstractController
{
    #[Route('/sample', name: 'sample')]
    public function sample(MessageBusInterface $bus): Response
    {
        $message = new SampleMessage('content');
        $bus->dispatch($message);

        return new Response(sprintf('Message with content %s was published', $message->getContent()));
    }
}
Enter fullscreen mode Exit fullscreen mode

Thankfully when I hit the endpoint, I can see the output from the handler and the http response from the controller:

curl http://localhost:8000/sample

Handler handled the message!Message with content content was published
Enter fullscreen mode Exit fullscreen mode

Where is RabbitMQ?

Gif of a Rabbit
Yeah so far there's not even a trace of RabbitMQ, but don't worry because I've prepared the terrain.

Install all dependencies

Docker image

I'll use docker to spawn a RabbitMQ instance, because it's much easier. Just install Docker Compose and then edit the .docker-compose.yml file on the project root directory to add a new service:

version: '3'

services:
  rabbitmq:
    image: rabbitmq:3.9-management
    ports:
      - '5672:5672'
      - '15672:15672'
Enter fullscreen mode Exit fullscreen mode

By running docker-compose up on project root directory, I can see everything is working.

PECL AMQP Extension

The AMQP (Advanced Message Queuing Protocol, the protocol which RabbitMQ uses) extension is needed to be installed using PECL (PHP Extension Community Language). This was a bit tricky, at least on MacOS:

First, installed HomeBrew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Enter fullscreen mode Exit fullscreen mode

Then installed rabbitmq-c:

brew install rabbitmq-c
Enter fullscreen mode Exit fullscreen mode

Which enabled the installation of amqp extension:

pecl install amqp
Enter fullscreen mode Exit fullscreen mode

When prompted to enter the path to librabbitmq, you need to check which version is installed inside the folder /usr/local/Cellar/rabbitmq-c/. Mine was 0.11.0:

Set the path to librabbitmq install prefix [autodetect] : /usr/local/Cellar/rabbitmq-c/0.11.0
Enter fullscreen mode Exit fullscreen mode

Finally, the last dependency:

composer require symfony/amqp-messenger 
Enter fullscreen mode Exit fullscreen mode

What a relief! Now I can go back to proper coding.

Using the asynchronous power

By default the username and password created in the docker image are guest, which is coincidentally the exact line I need to uncomment on .env file to expose the RabbitMQ connection as an environment variable:

###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=doctrine://default
 MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###
Enter fullscreen mode Exit fullscreen mode

Now with this new known value, I need to tell the application which messages should be handled by this new transport.

Then on file config/packages/messanger.yaml, I defined a new transport and the message type that will use it:

framework:
    messenger:
        transports:
            # https://symfony.com/doc/current/messenger.html#transport-configuration
            async: '%env(MESSENGER_TRANSPORT_DSN)%'

        routing:
            # Route your messages to the transports
            'App\Message\SampleMessage': async
Enter fullscreen mode Exit fullscreen mode

Now I can hit the previous endpoint again:

curl http://localhost:8000/sample
Enter fullscreen mode Exit fullscreen mode

And in another terminal I can check the message is still being handled:

php bin/console messenger:consume async -vv
Enter fullscreen mode Exit fullscreen mode

This outputs a verbose log message but the important parts are:

[messenger] Received message App\Message\SampleMessage
[messenger] App\Message\SampleMessage was handled successfully (acknowledging to transport).
[messenger] Message App\Message\SampleMessage handled by App\MessageHandler\SampleMessangeHandler
Enter fullscreen mode Exit fullscreen mode

To be sure the application is really using RabbitMQ, I can access the admin on http://localhost:15672 and see this cool chart:

Chart showing the message rate

Wrap up

Gif of someone happy
Finally I have a basic setup of RabbitMQ and Symfony! Now I can give life to a bunch of side projects and explore message queueing patterns. Hope you enjoyed and learned something in the way!

. . . . . . . . . . . . . . . . . . . . . . . . . . . .