Multiple Buses
A common architecture when building applications is to separate commands from queries. Commands are actions that do something and queries fetch data. This is called CQRS (Command Query Responsibility Segregation). See Martin Fowler's article about CQRS to learn more. This architecture could be used together with the Messenger component by defining multiple buses.
A command bus is a little different from a query bus. For example, command buses usually don't provide any results and query buses are rarely asynchronous. You can configure these buses and their rules by using middleware.
It might also be a good idea to separate actions from reactions by introducing an event bus. The event bus could have zero or more subscribers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
framework:
messenger:
# The bus that is going to be injected when injecting MessageBusInterface
default_bus: command.bus
buses:
command.bus:
middleware:
- validation
- doctrine_transaction
query.bus:
middleware:
- validation
event.bus:
# the 'allow_no_handlers' middleware allows to have no handler
# configured for this bus without throwing an exception
default_middleware: allow_no_handlers
middleware:
- validation
This will create three new services:
command.bus
: autowireable with the MessageBusInterface type-hint (because this is thedefault_bus
);query.bus
: autowireable withMessageBusInterface $queryBus
;event.bus
: autowireable withMessageBusInterface $eventBus
.
Restrict Handlers per Bus
By default, each handler will be available to handle messages on all
of your buses. To prevent dispatching a message to the wrong bus without an error,
you can restrict each handler to a specific bus using the messenger.message_handler
tag:
1 2 3 4 5 6 7
# config/services.yaml
services:
App\MessageHandler\SomeCommandHandler:
tags: [{ name: messenger.message_handler, bus: command.bus }]
# prevent handlers from being registered twice (or you can remove
# the MessageHandlerInterface that autoconfigure uses to find handlers)
autoconfigure: false
This way, the App\MessageHandler\SomeCommandHandler
handler will only be
known by the command.bus
bus.
You can also automatically add this tag to a number of classes by using the _instanceof service configuration. Using this, you can determine the message bus based on an implemented interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# config/services.yaml
services:
# ...
_instanceof:
# all services implementing the CommandHandlerInterface
# will be registered on the command.bus bus
App\MessageHandler\CommandHandlerInterface:
tags:
- { name: messenger.message_handler, bus: command.bus }
# while those implementing QueryHandlerInterface will be
# registered on the query.bus bus
App\MessageHandler\QueryHandlerInterface:
tags:
- { name: messenger.message_handler, bus: query.bus }
Debugging the Buses
The debug:messenger
command lists available messages & handlers per bus.
You can also restrict the list to a specific bus by providing its name as an argument.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
$ php bin/console debug:messenger
Messenger
=========
command.bus
-----------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
App\Message\DummyCommand
handled by App\MessageHandler\DummyCommandHandler
App\Message\MultipleBusesMessage
handled by App\MessageHandler\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
query.bus
---------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
App\Message\DummyQuery
handled by App\MessageHandler\DummyQueryHandler
App\Message\MultipleBusesMessage
handled by App\MessageHandler\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
Tip
Since Symfony 5.1, the command will also show the PHPDoc description of the message and handler classes.