This article will show a simpler practical example on how and why to use the Symfony4 Messenger component.
- (this article)
- Unit+Functional test the Symfony Messenger
- 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.
- “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
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 email@example.com:wuestkamp/symfony-messaging-queuing-example.gitcd symfony-messaging-queuing-example
git checkout step1 # in branch step2 this is all done already
bin/console doctrine:fixtures:load -n
If you do follow along paste whatever url the
server:run returns into your browser and fire it away:
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
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
To implement this its time for installing the messenger component:
composer require messenger
Next we will create two new directories:
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.
__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:
Now let’s create another booking again by calling
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
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
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:
Yes, we can set this up just for specific messages! We also need to define the
MESSENGER_TRANSPORT_DSN in the
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:fixtures:load -n
We also need to start the background worker process:
bin/console messenger:consume -vv
Now create a new booking
We were instantly redirected to
/bookings, but our booking is missing. That’s actually what we want! Just wait a few seconds and refresh:
Also check the worker process output:
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!