Say yes to the Symfony4 Messenger

Kim Wuestkamp
Jun 26, 2019 · 5 min read
Image for post
Image for post

This article will show a simpler practical example on how and why to use the Symfony4 Messenger component.

Parts

  1. (this article)
  2. Unit+Functional test the Symfony Messenger

TL;DR

  1. Think of “Messages” as actions your applications needs to do. Like “create a new booking” or “send the user an email notification”. Doing this helps you designing your application more abstract and maintainable.
  2. “Messages” are executed synchronously by default, so the application waits till those are finished. If later, or even from the beginning, some of your “Messages” will take too long, you can easily switch them to be asynchronous and handled by workers.

Step 0: a simple booking API without the Messenger

Image for post
Image for post
Simple Request -> Controller -> Response

This crazy API can list and create bookings. If the user creates a booking he’ll be redirected to the listing. Our BookingController looks like this:

You can also follow me along, just checkout the repo like this:

git clone git@github.com:wuestkamp/symfony-messaging-queuing-example.gitcd symfony-messaging-queuing-example
git checkout step1 # in branch step2 this is all done already
composer install
bin/console doctrine:schema:create
bin/console doctrine:fixtures:load -n
bin/console server:run

If you do follow along paste whatever url the server:run returns into your browser and fire it away:

Image for post
Image for post
Existing three bookings from our data fixtures

But this isn’t all the API can do! It can also create bookings. For this we call a url like /bookings/create/mybigtrip which creates a new booking and redirects back to /bookings.

Image for post
Image for post
call create booking url
Image for post
Image for post
redirects us to /bookings

Amazing I know. Please don’t steal my idea! (For commercial enquiries, licensing and a limited free trial version contact our closest strategy office)

Step 1: use the Messenger synchronously

Image for post
Image for post
Use a CreateBookingMessage to separate the creation logic

To implement this its time for installing the messenger component:

composer require messenger

Next we will create two new directories:

Image for post
Image for post

Inside Message create the file CreateBookingMessage.php , its just a simple class holding whatever information you like. In your case a $name string, because from this we can create a new Booking object.

Inside MessageHandler create the file CreateBookingMessageHandler.php , which is called every time we fire off a CreateBookingMessage using the messenger.

Inside the __invoke() method we simply create a new Booking with the help of the BookingRepository.

Now let’s use the messenger logic in our BookingController::create method and make it look like this:

As you can see, we aren’t using the BookingRepository here any longer

Now let’s create another booking again by calling /bookings/create/great-adventure

Image for post
Image for post
We got directly redirected to /bookings

Everything works just as before and is still executed synchronously, even though we called the $messageBus->dispatch() which sounds a bit “asyncy”.

So why all this? Did we just make things more complicated? Yes! Or did we?

What if stuff takes long ?

Let’s imagine the creation of a Booking takes some time, it needs to query some other services and needs to send requests a few times to the moon and back. We simulate this highly sophisticated scenario now by changing the CreateBookingMessageHandler:__invoke to:

Creating a new Booking takes now 5 long seconds.

Create another Booking by calling /bookings/create/holy-cow. It works, but it takes way too long!

Like the good engineers as we are, while planning the architecture of our application, we considered that this simple action might become more complex in the future.

Luckily we used the Symfony Messenger from the very beginning! Because now it’s just a matter of crashing some fancy yaml config and make the message handling async.

Step 2: use the Messenger in an async Queue

Image for post
Image for post
Thank you for reading this message. I see great potential in you.

We recognized that creating bookings takes too long for the user to wait. No one can be expected to wait 5 seconds! So we’ll handle this complex action asynchronously in the background. For this simply change the default messenger.yaml to this:

framework:
messenger:
transports:
async:
"%env(MESSENGER_TRANSPORT_DSN)%"
routing:
'App\Message\CreateBookingMessage':
async

Yes, we can set this up just for specific messages! We also need to define the MESSENGER_TRANSPORT_DSN in the .env:

MESSENGER_TRANSPORT_DSN=doctrine://default
Image for post
Image for post

Now every time a new CreateBookingMessage is fired, it will be handled asynchronously through a doctrine table. We need to create the new database structure for this:

bin/console doctrine:schema:drop --force
bin/console doctrine:schema:create
bin/console doctrine:fixtures:load -n

We also need to start the background worker process:

bin/console messenger:consume -vv

Now create a new booking /bookings/create/holiday-hopper

Image for post
Image for post
instant redirect, new booking nowhere to be found

We were instantly redirected to /bookings, but our booking is missing. That’s actually what we want! Just wait a few seconds and refresh:

Image for post
Image for post
after a few seconds wait and a refresh, we see the new booking

Also check the worker process output:

Image for post
Image for post
the worker output shows the handling of our CreateBookingMessage

Instead of showing nothing to the user obviously it would be better to create the Booking directly in a “pending” status, showing that to the user instantly. Then our async worker would work on the Booking and update its status.

For achieving this it would make sense to pass the ID of the Booking along with the Message and query the Booking object again in the Handler.

What did we see here?

When planning a new application or extension it can be very useful to start using the Symfony Messenger. Think of your applications actions as messenges and create handlers to work with those. If certain parts of your application are or become more complex, switch those to be handled async through a worker process.

Check out the Symfony Messenger Docs for more on this. We used the doctrine transport in our example, but as complexity rises it’s also easy to use other transports like AMQP and Redis.

I like this way too much!

FAUN

The Must-Read Publication for Creative Developers & DevOps Enthusiasts

Sign up for FAUN

By FAUN

Medium’s largest and most followed independent DevOps publication. Join thousands of aspiring developers and DevOps enthusiasts Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Kim Wuestkamp

Written by

wuestkamp.com | killer.sh (CKS CKA CKAD Simulator) | Software Engineer, Infrastructure Architect, Certified Kubernetes, Certified Symfony

FAUN

FAUN

The Must-Read Publication for Creative Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.

Kim Wuestkamp

Written by

wuestkamp.com | killer.sh (CKS CKA CKAD Simulator) | Software Engineer, Infrastructure Architect, Certified Kubernetes, Certified Symfony

FAUN

FAUN

The Must-Read Publication for Creative Developers & DevOps Enthusiasts. Medium’s largest DevOps publication.