Symfony. Introducción al componente Messenger (I)

Gerardo Fernández
Jul 16 · 6 min read

En este artículo quiero hacer una introducción al componente Messenger de Symfony el cual apareció junto a la versión 4.1 de Symfony y que nos permite simplifcar la forma en que trabajamos con eventos y colas. ¡Vamos a ello!

Conceptos básicos

El componente Messenger tal y como su propio nombre indica trabaja sobre mensajes que podemos entender como cualquier objeto que podamos serializar en PHP.

Estos mensajes son enviados al elemento que se conoce como Message bus y recibidos en última instancia por los Message handlers, donde se ejecuta la lógica de negocio asociados a ellos.

Otro de los conceptos claves a la hora de trabajar con este componente son los transports que nos van a permitir trabajar con sistemas de terceros para enviarles y recibir mensajes de ellos. Es decir, un transport nos permitirá, por ejemplo, integrar Messenger con las colas RabbitMQ de cara a encolar determinadas tareas. Y, asociado a los transports tendremos los workers los cuales se encargan de consumir los mensajes enviados por estos servicios de terceros a través de los transports.

En el caso de que no estemos integrados con un servicio de terceros como Rabbit, tendríamos una arquitectura similar a la siguiente:

Y, en el momento en que empezáramos a integrarnos con servicios externos, ya empezarían a aparecer los conceptos de worker y transports como podéis ver a continuación:

Ejemplo básico

Una vez entendido el esquema básico de Messenger, vamos a ver un ejemplo sencillo de como integrarlo dentro de nuestra aplicación. Supongamos que queremos implementar un sistema de notificaciones a usuarios, de modo que cada vez que sucedan determinadas acciones dentro del sistema podamos enviarles un mensaje con la información de lo sucedido.

Para ello, lo primero que haremos será instalar Symfony Messenger:

Gracias a la magia de Flex no tendremos mucho más que hacer a nivel de configuración pues ya de por sí nos vendrá configurado un Message Bus a través del cual enviar los mensajes.

A continuación, crearemos una clase llamada Notification que representa los mensajes que enviamos a través del bus:

Y finalmente probaremos a enviar una notificación por medio de nuestro message bus que viene configurado por defecto:

¿Sencillo verdad? Sin embargo, si tratamos de acceder a la ruta donde se está enviando nuestra notificación obtendremos una excepción del tipo NoHandlerForMessageException :

No handler for message “App\Message\Notification”

Es decir, el componente nos obliga a que dispongamos al menos un message handler por cada tipo de mensajes que tengamos, lo cual puede ayudarnos a prevenir ese tipo de errores tipográficos que tanto cuestan debuggear.

Así que, veamos cómo crear nuestro primer message handler.

Como veis, estamos creando una clase autoinvocable en donde gestionaremos cada mensaje de tipo Notification que recibamos. Ahora, para engancharla al componente Messenger lo que haremos será ir a nuestro archivo config/services.yaml y etiquetarla dentro del container con messenger.message_handler lo cual permitirá al componente identificarla como un message handler.

Y, puesto que nuestra clase NotificationHandler es autoinvocable, Messenger es capaz de detectar el tipo de messages que nuestro handler “está escuchando” (en nuestro caso de la clase App\Message\Notification ) por lo que no deberemos añadir nada más en la configuración del servicio.

Puesto que cada vez que declaremos un message handler deberemos etiquetarlo, existe un atajo para evitarnos repetir este proceso y es tener la siguiente configuración en nuestro services.yaml :

Con ello le estaremos indicando a Symfony que todo aquello que se encuentre dentro de la carpeta /src/MessageHanlder irá etiquetado con messenger.message_handler .

Finalmente, existe una tercera que nos permitirá ahorrarnos etiquetar los servicios por medio del archivo yaml y es aprovecharnos del autoconfigure de Symfony implementando la interfaz MessageHandlerInterface lo cual permitirá al compilador detectar nuestro message handler y tratarlo como tal sin necesidad de etiquetarlo:

Con todo esto ya podremos acceder a la ruta de nuestro controlador y ver que se imprime en nuestro navegador los mensajes Notification sent to que habíamos configurado en nuestro message handler.

Además, otra de las cosas que veremos si hemos instalado el componente Profiler es que tendremos una nueva pestaña desde donde podremos acceder a todos los mensajes que se han enviado y seguir el recorrido de los mismos.

Sin embargo… ¿qué sucede si cada notificación tarda mucho en enviarse? Es aquí donde entran en acción los workers y transports para permitirnos encolar dicho proceso y realizarlo de forma asíncrona sin que la respuesta tarde en llegar al usuario.

Symfony Messenger y RabbitMQ

Para este ejemplo supondremos que tenemos RabbitMQ instalado y corriendo en nuestro ordenador o servidor de pruebas.

Puesto que lo queremos es integrar Messenger con el servicio Rabbit, tendremos que recurrir a los transport. Las características principales de este elemento son las siguientes:

  • Tiene un sender y un receiver que se encarga de enviar y recibir los mensajes que pasan a través de él.
  • Es configurable vía DSN de modo que la forma de configurarlo es común a todos los servicios externos que empleemos (pensad en la forma en que configuramos Doctrine y la URL de la base de datos).
  • Emplea un Messenger Serializer que por defecto es el de Symfony (pero por supuesto podemos cambiarlo por otro que nos guste más).

Para declarar un transport introduciremos las siguientes líneas en nuestro archivo config/packages/messenger.yaml :

Y añadiremos la variable MESSENGER_DSN en nuestro archivo .env , por ejemplo:

A continuación, tendremos que especificar a que transport se envía cada message que queramos enviar, para lo cual añadiremos lo siguiente:

De este modo, cada message de la clase Notification será enviado al transport default. La buena noticia es que si estamos trabajando con interfaces o clases abstractas podremos asociar todas las clases que las implementen / extiendan de una sola vez asociando dicha interfaz / clase abstracta al transport que queramos.

Con esta configuración si ahora accedemos a nuestra ruta anterior veremos que ya no se pinta en nuestro navegador el mensaje Notification sent to puesto que este mensaje ahora no habrá sido pasado al message handler sino al transport. Es decir, éste es el camino que ha seguido ahora nuestro mensaje:

¿Y cómo conseguimos que ahora se envíe la notificación? Por medio del elemento worker que veremos a continuación.

Consumiendo mensajes

El componente Messenger trae consigo un comando de consola que permite consumir mediante un worker los mensajes que hayamos encolado:

Que básicamente lo que haces es avisar al transport de que le notifique cuando tenga nuevos mensajes que consumir y para cada mensaje así recibido lo vuelve a enviar al message bus para que sea procesado por su correspondiente message handler.

Por tanto, si ahora ejecutamos dicho comando:

Obtendremos por consola:

Y con esto terminaría esta primera parte acerca del Messenger Component. En la siguiente veremos cómo podemos declarar diferentes buses para gestionar distintos tipos de mensajes y profundizaremos en los message handlers los cuales tienen algunos trucos que siempre está bien conocer.

¿Quieres recibir más artículos como este?

Suscríbete a nuestra newsletter:

Gerardo Fernández

Written by

Entre paseo y paseo con Simba desarrollo en Symfony y React

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade