From 9973d671bfe44791ef125fb2342a7c1c9e7b9ef1 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Wed, 4 Jul 2018 16:59:52 +0200 Subject: [PATCH 1/2] Added configuration for multiple buses --- messenger/multiple-buses.rst | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 messenger/multiple-buses.rst diff --git a/messenger/multiple-buses.rst b/messenger/multiple-buses.rst new file mode 100644 index 00000000000..2cf010995c2 --- /dev/null +++ b/messenger/multiple-buses.rst @@ -0,0 +1,41 @@ +.. index:: + single: Messenger; Multiple buses + +Multiple Buses +============== + +A common architecture when building application is to separate commands from +queries. Commands are actions that do something and queries fetches 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 form a **query bus**. As example, +command buses must not return anything and query buses are rarely asynchronous. +You can configure these buses and their rules by using middlewares. + +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. + +.. configuration-block:: + + .. code-block:: yaml + + framework: + messenger: + default_bus: messenger.bus.command + buses: + messenger.bus.command: + middleware: + - messenger.middleware.validation + - messenger.middleware.handles_recorded_messages: ['@messenger.bus.event'] + - doctrine_transaction_middleware: ['default'] + messenger.bus.query: + middleware: + - messenger.middleware.validation + messenger.bus.event: + middleware: + - messenger.middleware.allow_no_handler + - messenger.middleware.validation + +.. _article about CQRS: https://martinfowler.com/bliki/CQRS.html From 4a3f754e43e5f41f3dab74666760a484a9b1f868 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Tue, 20 Nov 2018 14:24:57 +0100 Subject: [PATCH 2/2] [Messenger] Multiples buses, scoping handlers per bus, PSR-4 discovery & debug --- components/messenger.rst | 9 ++ messenger.rst | 114 +------------- messenger/multiple-buses.rst | 41 ----- messenger/multiple_buses.rst | 292 +++++++++++++++++++++++++++++++++++ 4 files changed, 309 insertions(+), 147 deletions(-) delete mode 100644 messenger/multiple-buses.rst create mode 100644 messenger/multiple_buses.rst diff --git a/components/messenger.rst b/components/messenger.rst index 2700e3eec55..06d618190a0 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -291,5 +291,14 @@ loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Asynchro envelope item to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Asynchronous\\Middleware\\SendMessageMiddleware` middleware will know it should not route these messages again to a transport. +Learn more +---------- +.. toctree:: + :maxdepth: 1 + :glob: + + /messenger + /messenger/* + .. _blog posts about command buses: https://matthiasnoback.nl/tags/command%20bus/ .. _SimpleBus project: http://simplebus.io diff --git a/messenger.rst b/messenger.rst index 14f5f4d906e..70e8926114d 100644 --- a/messenger.rst +++ b/messenger.rst @@ -421,112 +421,6 @@ like this: The first argument is the receiver's service name. It might have been created by your ``transports`` configuration or it can be your own receiver. -Multiple Buses --------------- - -If you are interested in architectures like CQRS, you might want to have multiple -buses within your application. - -You can create multiple buses (in this example, a command bus and an event bus) like -this: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/messenger.yaml - framework: - messenger: - # The bus that is going to be injected when injecting MessageBusInterface: - default_bus: messenger.bus.commands - - # Create buses - buses: - messenger.bus.commands: ~ - messenger.bus.events: ~ - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // config/packages/messenger.php - $container->loadFromExtension('framework', array( - 'messenger' => array( - 'default_bus' => 'messenger.bus.commands', - 'buses' => array( - 'messenger.bus.commands' => null, - 'messenger.bus.events' => null, - ), - ), - )); - -This will generate the ``messenger.bus.commands`` and ``messenger.bus.events`` services -that you can inject in your services. - -.. note:: - - To register a handler only for a specific bus, add a ``bus`` attribute to - the handler's service tag (``messenger.message_handler``) and use the bus - name as its value. - -Type-hints and Auto-wiring -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Auto-wiring is a great feature that allows you to reduce the amount of configuration -required for your service container to be created. When using multiple buses, by default, -the auto-wiring will not work as it won't know which bus to inject in your own services. - -In order to clarify this, you can use the DependencyInjection's binding capabilities -to clarify which bus will be injected based on the argument's name: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - _defaults: - # ... - - bind: - $commandBus: '@messenger.bus.commands' - $eventBus: '@messenger.bus.events' - - .. code-block:: xml - - - - - - - - - - - - - Middleware ---------- @@ -961,4 +855,12 @@ will give you access to the following services: #. ``messenger.sender.yours``: the sender; #. ``messenger.receiver.yours``: the receiver. +Learn more +---------- +.. toctree:: + :maxdepth: 1 + :glob: + + /messenger/* + .. _`enqueue's transport`: https://github.com/php-enqueue/messenger-adapter diff --git a/messenger/multiple-buses.rst b/messenger/multiple-buses.rst deleted file mode 100644 index 2cf010995c2..00000000000 --- a/messenger/multiple-buses.rst +++ /dev/null @@ -1,41 +0,0 @@ -.. index:: - single: Messenger; Multiple buses - -Multiple Buses -============== - -A common architecture when building application is to separate commands from -queries. Commands are actions that do something and queries fetches 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 form a **query bus**. As example, -command buses must not return anything and query buses are rarely asynchronous. -You can configure these buses and their rules by using middlewares. - -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. - -.. configuration-block:: - - .. code-block:: yaml - - framework: - messenger: - default_bus: messenger.bus.command - buses: - messenger.bus.command: - middleware: - - messenger.middleware.validation - - messenger.middleware.handles_recorded_messages: ['@messenger.bus.event'] - - doctrine_transaction_middleware: ['default'] - messenger.bus.query: - middleware: - - messenger.middleware.validation - messenger.bus.event: - middleware: - - messenger.middleware.allow_no_handler - - messenger.middleware.validation - -.. _article about CQRS: https://martinfowler.com/bliki/CQRS.html diff --git a/messenger/multiple_buses.rst b/messenger/multiple_buses.rst new file mode 100644 index 00000000000..e3b221450d6 --- /dev/null +++ b/messenger/multiple_buses.rst @@ -0,0 +1,292 @@ +.. index:: + single: Messenger; Multiple buses + +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. + +.. configuration-block:: + + .. code-block:: yaml + + framework: + messenger: + # The bus that is going to be injected when injecting MessageBusInterface + default_bus: messenger.bus.commands + buses: + messenger.bus.commands: + middleware: + - validation + - doctrine_transaction + messenger.bus.queries: + middleware: + - validation + messenger.bus.events: + middleware: + - allow_no_handler + - validation + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + // The bus that is going to be injected when injecting MessageBusInterface + 'default_bus' => 'messenger.bus.commands', + 'buses' => array( + 'messenger.bus.commands' => array( + 'middleware' => array( + 'validation', + 'doctrine_transaction', + ), + ), + 'messenger.bus.queries' => array( + 'middleware' => array( + 'validation', + ), + ), + 'messenger.bus.events' => array( + 'middleware' => array( + 'validation', + 'allow_no_handler', + ), + ), + ), + ), + )); + +This will generate the ``messenger.bus.commands``, ``messenger.bus.queries`` +and ``messenger.bus.events`` services that you can inject in your services. + +Type-hints and Auto-wiring +-------------------------- + +Auto-wiring is a great feature that allows you to reduce the amount of configuration +required for your service container to be created. By using ``MessageBusInterface`` +as argument typehint in your services, the default configured bus will be injected +(i.e ``messenger.bus.commands`` in above examples). + +When working with multiple buses, you can use the ``DependencyInjection`` component's +binding capabilities to clarify which bus will be injected based on the argument's name: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + _defaults: + # ... + + bind: + $commandBus: '@messenger.bus.commands' + $queryBus: '@messenger.bus.queries' + $eventBus: '@messenger.bus.events' + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + + $container->bind('$commandBus', 'messenger.bus.commands'); + $container->bind('$queryBus', 'messenger.bus.queries'); + $container->bind('$eventBus', 'messenger.bus.events'); + +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: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\MessageHandler\SomeCommandHandler: + tags: [{ name: messenger.message_handler, bus: messenger.bus.commands }] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + + $container->services() + ->set(App\MessageHandler\SomeCommandHandler::class) + ->tag('messenger.message_handler', ['bus' => 'messenger.bus.commands']); + +This way, the ``App\MessageHandler\SomeCommandHandler`` handler will only be +known by the ``messenger.bus.commands`` bus. + +You can also automatically add this tag to a number of classes by following +a naming convention and registering all of the handler services by name with +the correct tag: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + + # put this after the `App\` line that registers all your services + command_handlers: + namespace: App\MessageHandler\ + resource: '%kernel.project_dir%/src/MessageHandler/*CommandHandler.php' + tags: + - { name: messenger.message_handler, bus: messenger.bus.commands } + + query_handlers: + namespace: App\MessageHandler\ + resource: '%kernel.project_dir%/src/MessageHandler/*QueryHandler.php' + tags: + - { name: messenger.message_handler, bus: messenger.bus.queries } + + .. code-block:: xml + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + + // Command handlers + $container->services() + ->load('App\MessageHandler\\', '%kernel.project_dir%/src/MessageHandler/*CommandHandler.php') + ->tag('messenger.message_handler', ['bus' => 'messenger.bus.commands']); + + // Query handlers + $container->services() + ->load('App\MessageHandler\\', '%kernel.project_dir%/src/MessageHandler/*QueryHandler.php') + ->tag('messenger.message_handler', ['bus' => 'messenger.bus.queries']); + +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 argument. + +.. code-block:: terminal + + $ bin/console debug:messenger + + Messenger + ========= + + messenger.bus.commands + ---------------------- + + The following messages can be dispatched: + + --------------------------------------------------------------------------------------- + App\Message\DummyCommand + handled by App\MessageHandler\DummyCommandHandler + App\Message\MultipleBusesMessage + handled by App\MessageHandler\MultipleBusesMessageHandler + --------------------------------------------------------------------------------------- + + messenger.bus.queries + --------------------- + + The following messages can be dispatched: + + --------------------------------------------------------------------------------------- + App\Message\DummyQuery + handled by App\MessageHandler\DummyQueryHandler + App\Message\MultipleBusesMessage + handled by App\MessageHandler\MultipleBusesMessageHandler + --------------------------------------------------------------------------------------- + +.. _article about CQRS: https://martinfowler.com/bliki/CQRS.html