Qué es Symfony Messenger y cómo podemos utilizarlo en nuestros proyectos

En la versión 4.1 se introdujo un nuevo componente llamado Symfony Messenger que nos proporciona una manera sencilla de interactuar con un bus de mensajes y configurar distintos sistemas de colas como transporte de manera que podemos comunicar nuestra aplicación con otras aplicaciones o distintos componentes de nuestra aplicación a través de dichas colas. En este post vas a descubrir qué es Symfony Messenger y cómo podemos utilizarlo en tus proyectos.

¿Qué podemos hacer con el componente Messenger?

Para empezar lo que podemos hacer es desacoplar el código y delegar partes pesadas en una aplicación en un worker que ejecute ese código de manera asíncrona e independiente de la petición HTTP. Ejemplos clásicos de uso son el envío de correos o generación de documentos, enviar un mensaje al bus es una operación muy rápida y nos permite mostrar al usuario la página de agradecimiento tras su pedido rápidamente, mientras que de manera asíncrona un worker consumirá el mensaje y le enviará la factura.

Con este sistema si algo fallara en el envío de la factura no afectaría a la página de confirmación del pedido ni tendría que esperar ese tiempo adicional.

Imagen que describe el proceso

¿Cómo usamos el Messenger?

Aunque Symfony Messenger se puede utilizar como componente independiente es una pieza que normalmente se utiliza integrada dentro del framework Symfony dado que normalmente cuando una aplicación llega al grado de complejidad en el que se valora utilizar este tipo de soluciones normalmente es porque no se trata de un script con el que realizar tareas sencillas sino de una aplicación cuya complejidad ya saca partido del resto de funcionalidades que aporta el propio framework.

Instalación

Por tanto partiremos de un proyecto Symfony que podemos crear por ejemplo con cualquiera de los métodos refleja la documentación, en nuestro caso con composer:

Una vez tengamos nuestra aplicación únicamente tendremos que requerir el paquete correspondiente al componente Messenger:

Elementos clave

Ya tenemos instalado el paquete y los siguientes términos y clases son los utilizaremos a partir de este punto para implementar nuestra solución:

  • Message: Es el objeto que se transmite y que alberga la información necesaria para ejecutar la acción en última instancia.
  • Message bus: Es el canal a través del que enviamos los mensajes y se ejecutan los handlers correspondientes, nos permite también enviar los mensajes a transportes externos como Redis.
  • Transport: Es el sistema a través del que viajan los mensajes, el transport sync no sacaría el mensaje fuera de la aplicación y ejecutaría su handler de manera instantánea pero podemos utilizar sistemas externos como RabbitMQ o Redis
  • Message handler: Es la clase que ejecuta la lógica asociada a un mensaje, recibe el mensaje y utiliza la información para procesarla y realizar las acciones necesarias, por ejemplo enviar un correo electrónico.
  • Worker: Es un proceso que nos permite consumir los mensajes que hay en colas, por ejemplo de RabbitMQ como mencionábamos anteriormente.

Casos de uso

El caso más sencillo es utilizar el messenger para desacoplar el código y reutilizarlo, si no configuramos ningún transporte asíncrono por defecto tendremos configurado sync. Con este transporte los handlers se ejecutarán instantáneamente y de manera síncrona.

Con el transporte por defecto no conseguimos independizar el proceso actual de procesos paralelos que puedan ser más pesados y que retrasen de manera innecesaria la respuesta al usuario pero sí organizar nuestro código de una manera bastante limpia y preparada para escalar en un futuro.

Ilustración "Fast Delivery"

Para nuestro ejemplo hemos creado un controller con Symfony MakerBundle y nos ha sugerido llamarlo AgreeablePizzaController ¿A todos nos gustan las pizzas no 🍕? Desde el controller mostraremos el mensaje al usuario y lanzaremos el lento proceso de notificar al usuario de que su pizza está en camino.

Para implementar esta funcionalidad simplemente tendremos que crear una clase que contenga la información del mensaje y otra que lo procese, es decir, un Message y un Message handler. En nuestro ejemplo creamos las siguientes clases:

La implementación del envío corre a cargo de App\FancyNotification\FancyNotifier que en nuestro caso no tiene más lógica que un sleep para simular ese proceso pesado del que hemos estado hablando.

Gracias las características de autoconfigure y autowire de las versiones recientes de Symfony hemos podido hacer todo esto sin crear una línea de configuración, hemos inyectado las dependencias necesarias en nuestro handler, hemos definido los servicios de esas dependencias y hemos asociado el mensaje con su handler correspondiente.

Ahora que tenemos nuestro mensaje, el handler asociado y ese proceso pesado que queremos eliminar de la petición que realiza el usuario para darle la respuesta lo antes posible podemos enviar el mensaje a través del message bus en el controller para que se ejecute.

Para inyectar el message bus simplemente tendremos que añadir un parámetro al action del controller con el type hint Symfony\Component\Messenger\MessageBusInterface y para enviar el mensaje los instanciaremos y pasaremos como parámetro al método dispatch del bus como vemos a continuación.

Si ejecutamos nuestro controller veremos que sigue siendo lento pero como planteábamos anteriormente utilizar el componente nos ha forzado en cierta manera a desacoplar el código, para hacer que la notificación se envíe de manera independiente y asíncrona vamos a configurar un transporte asíncrono, en nuestro caso utilizaremos el transporte doctrine por simplicidad, si utilizamos Redis por ejemplo además necesitaremos tener instalada la extensión php-redis.

Para ello tendremos que configurar la variable de entorno MESSENGER_TRANSPORT_DSN con el valor adecuado, en nuestro caso estamos ejecutando el proyecto usando docker-compose y la configuración correspondiente sería la siguiente

Una vez configurado el DSN del transporte debemos modificar el fichero config/packages/messenger.yaml para obtener algo similar a los siguiente


Lo que hemos hecho ha sido descomentar el transporte async que utiliza la variable de entorno que acabamos de definir y enrutar nuestros mensajes al transporte async en la sección de routing.

Si volvemos a cargar el controlador veremos que la ejecución es mucho más rápida, esto es porque el mensaje se ha enviado al transporte pero no se ha ejecutado el handler asociado. Para ello tendremos que ejecutar un worker como un proceso independiente utilizando el comando:

En este caso utilizamos el flag -vv para que nos muestre en el terminal información sobre los mensajes que se van consumiendo y obtendremos un log con mensajes similares a los siguientes en los que se especifica el mensaje, el handler ejecutado y el resultado:

Conclusiones

Como hemos visto empezar a utilizar el componente Messenger no es complicado, nos ayudará a plantearnos cómo desacoplar algunas partes de código y a estar preparados de cara al futuro a delegar esos procesos en ejecuciones asíncronas.

Una de las grandes ventajas de utilizar este componente es que una vez empezamos a utilizar Redis o RabbitMQ como transport y los workers para ejecutar las tareas pesadas que se pueden posponer de nuestra aplicación ganaremos enteros en escalabilidad ya que esas tareas pesadas pasan a workers y podemos instanciar tantos como sea necesario.

Si tienes dudas o crees que tu sistema puede beneficiarse de las técnicas o herramientas que hemos descrito no dudes en contactar con nosotros.

Comparte
Share on facebook
Share on twitter
Share on linkedin
¿Quieres más información?
Ponte en contacto con nosotros.
Christian Córdoba
Christian Córdoba
Chief Technology Officer (CTO) / Backend developer - Departamento de desarrollo

Enviar Comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Suscríbete a nuestra newsletter
para estar al día en el mundo online
¿Tienes alguna incidencia?

Cuéntanos qué ocurre
y nos pondremos con ello lo antes posible.

Este sitio está protegido por reCAPTCHA, y la Política de privacidad y Términos de servicio de Google.
¡Cuéntanos tus ideas!
+34 96 653 19 14
info@acceseo.com
Este sitio está protegido por reCAPTCHA, y la Política de privacidad y Términos de servicio de Google.