The Messenger Component is a powerful and easy tool for building all kinds of message buses. It was created as a part of the Symfony ecosystem but it doesn’t mean that Messenger can be used only with Symfony. In this post, I’m going to show you the basic elements of the component and explain how it works. In the next post in this series, I’m going to show you how to implement basic message buses with both Symfony and Laravel projects.
Let’s get started from the very beginning. There are some basic concepts you need to understand.
Message
A message is a simple object representing an intent (command and query) or an event that occurred in the system. Usually, a message contains only fields and getters, without any business logic.
<?php namespace App\Command; use Ramsey\Uuid\Uuid; class CreatePost { private $postId; private $postTitle; private $postContent; public function __construct(Uuid $postId, string $postTitle, string $postContent) { $this->postId = $postId; $this->postTitle = $postTitle; $this->postContent = $postContent; } public function getPostId(): Uuid { return $this->postId; } public function getPostTitle(): string { return $this->postTitle; } public function getPostContent(): string { return $this->postContent; } }
Message handler
Message handler, which like its name suggests, is a place where messages are handled. It’s the place where a message can be validated and all required operations are implemented (e.g. creating, deleting, modifying or persisting objects). Message handlers can also dispatch other messages.
<?php namespace App\Command\Handler; use App\Command\CreatePost; use App\Entity\Post; use App\Repository\PostRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; class CreatePostHandler implements MessageHandlerInterface { private $posts; private $em; public function __construct(PostRepository $postRepository, EntityManagerInterface $em) { $this->posts = $postRepository; $this->em = $em; } public function __invoke(CreatePost $command) { $postId = $command->getPostId(); $postTitle = $command->getPostTitle(); $postContent = $command->getPostContent(); $post = new Post($postId, $postTitle, $postContent); $this->em->persist($post); $this->em->flush(); } }
Envelope
An envelope is just a simple container that contains only a message and stamps attached to it. If you don’t want to attach any custom stamps to your message, you don’t even have to create the envelope on your own, it is going to be created automatically when you dispatch a message. Here is the example of a manually created envelope with the attached BusNameStamp.
$envelope = (new Envelope($command))->with(new BusNameStamp("command.bus")); $commandBus->dispatch($envelope);
Stamp
Stamps are really small classes that allow you to attach some additional pieces of information you can later use in middlewares, senders and receivers. You can find sample built-in stamps here.
Middleware
Middlewares are executed by message bus before dispatching messages to their handlers. In a middleware, you can e.g. validate a message before handling it (check ValidationMiddleware) or provide any other operation on the message or its stamps.
Message bus
The message bus is responsible for dispatching messages to their handlers. Its behavior mainly depends on middlewares that are supplied to it.
Transport
Transport is responsible for storing and retrieving messages from the store. It is needed only when message should be handled asynchronously. Transports consist of senders (e.g. DoctrineSender) and receivers (e.g. DoctrineReceiver).
The most common message buses
There are many possible specifications of message buses but I’m going to focus on the three most common. For ease and simplicity of understanding, I’ll be using standard colors (based on Event Storming methodology) for main elements – blue for commands, green for queries and orange for events.
Command bus
The command bus is usually synchronous and doesn’t return anything. According to the standard approach to the command bus pattern, there should be exactly one handler for each command. If any operation fails, the handler throws an exception that can be caught in a higher layer.
Query bus
The query bus is always synchronous and returns a value. Same as in command bus pattern, one query corresponds to exactly one handler.
Event bus
The event bus can be either synchronous or asynchronous (the latter one is more common) and doesn’t return anything. Contrary to the other bus types, every event can correspond to any quantity of handlers (including zero). The asynchronous version of the event bus requires an event store where events are stored until they are consumed. The Messenger by default supports three types of event stores basing on Doctrine, Redis, and AMQP but it is possible to implement your own store.
Summary
This post contains only a quick introduction to The Messenger Component. If you want to learn more about it take a look at official documentation or a video tutorial on Symfonycasts. You can also check my other posts related to this topic: