Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Organización del bus de eventos y comandos

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ya hemos organizado nuestra nueva clase de eventos en un subdirectorio Event. ¡Genial! Hagamos lo mismo con nuestros comandos. Crea un nuevo subdirectorio Command/, mueve las dos clases de comandos dentro... y añade \Command al final del espacio de nombres de ambas clases.

Veamos... ahora que hemos cambiado esos espacios de nombres... tenemos que actualizar algunas cosas. Empieza en messenger.yaml: estamos haciendo referencia a AddPonkaToImage. Añade Command a ese nombre de clase. A continuación, en ImagePostController, arriba del todo, estamos haciendo referencia a los dos comandos. Actualiza el espacio de nombres en cada uno de ellos.

Y por último, en los manejadores, tenemos lo mismo: cada manejador tiene una declaración use para la clase de comando que maneja. Añade el espacio de nombres Command\ en ambos.

¡Genial! Hagamos lo mismo con los manejadores: crea un nuevo subdirectorio llamado Command/, muévelos dentro... y añade el espacio de nombres \Command a cada uno. Eso es... todo lo que tenemos que cambiar.

¡Me gusta! Este cambio no tiene nada de técnico... sólo es una buena forma de organizar las cosas si piensas utilizar algo más que comandos, es decir, eventos o mensajes de consulta. Y todo funcionará exactamente igual que antes. Para comprobarlo, en tu terminal, ejecuta debug:messenger:

php bin/console debug:messenger

¡Sí! Vemos la misma información que antes.

Vinculación de los manejadores a un bus

Pero... ahora que hemos separado nuestros manejadores de eventos de nuestros manejadores de comandos... podemos hacer algo especial: podemos vincular cada manejador al bus específico al que está destinado. De nuevo, no es superimportante hacer esto, pero hará que las cosas estén más claras.

Te lo mostraré: abre config/services.yaml. Esta línea App\ es la responsable de registrar automáticamente cada clase del directorio src/ como un servicio en el contenedor.

La línea siguiente repite eso para las clases del directorio Controller/. ¿Por qué? Esto anulará los servicios del controlador registrados anteriormente y añadirá una etiqueta especial que los controladores necesitan para funcionar.

Podemos utilizar un truco similar con Messenger. Digamos App\MessageHandler\Command\, y luego utilizar la tecla resource para volver a registrar todas las clases del directorio ../src/MessageHandler/Command. Uy, me he equivocado con el nombre del directorio... Veré un gran error en unos minutos... y lo arreglaré.

... lines 1 - 7
services:
... lines 9 - 29
App\MessageHandler\Command\:
resource: '../src/MessageHandler/Command'
... lines 32 - 44

Si sólo hiciéramos esto... no cambiaría absolutamente nada. Esto registraría todo lo que hay en este directorio como un servicio... pero eso ya lo hace la primera entrada de App\ de todos modos.

Pero ahora podemos añadir una etiqueta a esto con name: messenger.message_handler y bus: configurada con... el nombre de mi bus de messenger.yaml. Copia messenger.bus.default y di bus: messenger.bus.default.

... lines 1 - 7
services:
... lines 9 - 29
App\MessageHandler\Command\:
resource: '../src/MessageHandler/Command'
... line 32
tags: [{ name: messenger.message_handler, bus: messenger.bus.default }]
... lines 34 - 44

Aquí ocurren varias cosas. Primero, cuando Symfony ve una clase en nuestro código que implementa MessageHandlerInterface, añade automáticamente esta etiqueta messenger.message_handler. Así es como Messenger sabe qué clases son manejadoras de mensajes.

Ahora estamos añadiendo esa etiqueta manualmente para poder decir también exactamente en qué bus se debe utilizar este manejador. Sin la opción bus, se añade a todos los buses.

También tenemos que añadir una clave más: autoconfigure: false.

... lines 1 - 7
services:
... lines 9 - 29
App\MessageHandler\Command\:
resource: '../src/MessageHandler/Command'
autoconfigure: false
tags: [{ name: messenger.message_handler, bus: messenger.bus.default }]
... lines 34 - 44

Gracias a la sección _defaults de la parte superior, todos los servicios de nuestro directorio src/ tendrán, por defecto, activada la opción autoconfigure... que es la responsable de añadir automáticamente la etiqueta messenger.message_handler a todos los servicios que implementen MessageHandlerInterface. La desactivamos para los servicios de este directorio para que la etiqueta no se añada dos veces.

¡Ufff! Puedes ver el resultado final ejecutando de nuevo debug:messenger.

php bin/console debug:messenger

¡Oh, el resultado final es un gran error gracias a mi errata! Asegúrate de que estás haciendo referencia al directorio MessageHandler. Prueba de nuevo con debug:messenger:

php bin/console debug:messenger

¡Bien! El bus de eventos ya no dice que podamos enviar los dos comandos. Lo que realmente significa es que los manejadores de comandos se añadieron al bus de comandos, pero no al bus de eventos.

Repitamos esto para los eventos: copia esta sección, pégala, cambia el espacio de nombres a Event\, el directorio a Event y actualiza la opción bus aevent.bus -el nombre de nuestro otro bus dentro de messenger.yaml.

... lines 1 - 7
services:
... lines 9 - 34
App\MessageHandler\Event\:
resource: '../src/MessageHandler/Event'
autoconfigure: false
tags: [{ name: messenger.message_handler, bus: event.bus }]
... lines 39 - 44

¡Genial! Prueba de nuevo con debug:messenger:

php bin/console debug:messenger

¡Perfecto! Nuestros dos manejadores de comandos están vinculados al bus de comandos y nuestro único manejador de eventos está vinculado al bus de eventos.

Una vez más, hacer este último paso no era tan importante... pero me gustan mucho estos subdirectorios... y ajustar las cosas es agradable.

Cambiar el nombre del bus de comandos

Ah, pero mientras limpiamos las cosas, de vuelta en config/packages/messenger.yaml, nuestro bus principal se llama messenger.bus.default, que se convierte en el id de servicio del bus en el contenedor. Usamos este nombre... sólo porque ese es el valor por defecto que usa Symfony cuando sólo tienes un bus. Pero como este es un bus de comandos, ¡llamémoslo así! Cámbiale el nombre a command.bus. Y arriba, utiliza eso como nuestro default_bus.

framework:
messenger:
default_bus: command.bus
buses:
command.bus:
... lines 7 - 35

¿Dónde estaba la antigua clave referenciada en nuestro código? Gracias a que autoconducimos ese servicio a través de su tipo-indicación... casi en ningún sitio: sólo en services.yaml. Cambia también la opción del bus a command.bus.

... lines 1 - 7
services:
... lines 9 - 29
App\MessageHandler\Command\:
... lines 31 - 32
tags: [{ name: messenger.message_handler, bus: command.bus }]
... lines 34 - 44

Comprueba todo ejecutando debug:messenger una vez más:

php bin/console debug:messenger

Qué bien: dos buses, cada uno con un gran nombre y que sólo conocen los manejadores correctos.

Ah, y este AuditMiddleware es algo que realmente deberíamos utilizar también en event.bus: registra el recorrido de los mensajes... lo que es igualmente válido aquí.

framework:
messenger:
... lines 3 - 4
buses:
... lines 6 - 9
event.bus:
... line 11
middleware:
- App\Messenger\AuditMiddleware
... lines 14 - 37

Si te gusta esta organización, ¡genial! Si te parece demasiado, no te compliques: Messenger está aquí para hacer lo que tú quieras. A continuación, vamos a hablar del último tipo de bus de mensajes: el bus de consulta.

Leave a comment!

4
Login or Register to join the conversation
Default user avatar
Default user avatar Gianluca | posted hace 1 año

We are using two bus for command and event; let you have an Handler who react to an Event. This handler has to perform an action that usually is handled by a Command but after it has to dispatch an Event.
Example: we have a microservices who send SMS. There is a main command "SendSMSCommand"
Another MS need to send an SMS when dispatching an Event. This Event is catched from SMS with a dedicated Handler, who has to inject: eventBus AND commandBus, cause it has to use CommandBus to handle the sending of SMS, and eventBus to throw an new SmsSentEvent. Is it correct? To use two buses in an EventHandler?

1 Reply

Hey Gianluca

Yea, I don't see a problem injecting two Message busses into the same service, as long as you have a valid use-case

Cheers!

Reply
Dang Avatar
Dang Avatar Dang | posted hace 1 mes | edited

Hello guys,

In Symfony 6.2 when I run php bin/console debug:messenger, I see there a bunch of components like Symfony\Component\Mailer\Messenger\SendEmailMessage, Symfony\Component\Notifier\Message\PushMessage ... go to all the bus that I have. The problem is that I have a bus (say bus A) with a middleware which have logic only for one message handler. So if I understand correctly, the SendEmailMessage for instance could go to my bus A, through the middleware of that bus A and crash. If that's correct how can I prevent it, is there a way to say my bus A is for only one kind of message and no one else should go through it?

Thanks

Reply

Hey @Dang!

Hmm. Let's think about this. There are 2 ways for a message to be dispatched through a bus:

A) YOU are dispatching it directly in your code. So you have control of which message bus you're using.
B) Some 3rd party code (e.g. Symfony) dispatches the message. In this case, by convention, Symfony always uses the "main" message bus. Well, more precisely, it uses the main message bus, unless you tell it otherwise: https://github.com/symfony/symfony/blob/43066ff98d158946f6b5f8473802c9301f0abf49/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L2584-L2589 - the same is true for notifier - https://github.com/symfony/symfony/blob/43066ff98d158946f6b5f8473802c9301f0abf49/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L2692-L2696

Via your own code or this config, you can, in practice, control which messages go to which bus. However, on a purely theoretical level, there would still be nothing stopping something from dispatching the "wrong" message into the wrong bus. And that's why you see debug:messenger reporting things like SendEmailMessage under your bus (because it is theoretically possible for this to happen). There's no way to strictly prevent this (someone could always grab your bus and call ->dispatch() on it), but if you're concerned, I would add a middleware to your bus that detects if an "invalid" message is dispatched and throws an exception. It would still "crash" but at least in a controlled way (and then ideally you'd be notified and could fix whatever bug caused this).

Anyway, I hope this explanation helps!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

Este tutorial está construido con Symfony 4.3, pero funcionará bien en Symfony 4.4 o 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // v1.8.0
        "doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
        "doctrine/orm": "^2.5.11", // v2.6.3
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.9", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/property-access": "4.3.*", // v4.3.2
        "symfony/property-info": "4.3.*", // v4.3.2
        "symfony/serializer": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.7
        "symfony/debug-bundle": "4.3.*", // v4.3.2
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/var-dumper": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2
    }
}
userVoice