Cómo usar Symfony Messenger en formato JSON

Symfony Messenger es un componente que nos permite enviar mensajes a otros sistemas no PHP. En este artículo veremos cómo configurar Symfony Messenger para enviar mensajes en formato JSON.
Cómo usar Symfony Messenger en formato JSON

Symfony Messenger en formato JSON

El componente Symfony Messenger nos permite conectarnos y gestionar un bus de mensajes, o dicho de otro modo, nos da la oportunidad de procesar estos mensajes de forma síncrona (como los eventos dentro de una aplicación) o de forma asíncrona mediante una cola de mensajes. De esta manera podemos hacer tareas pesadas, como enviar un email, después de que la petición HTTP haya terminado. Pero también nos permite enviar mensajes a otros sistemas externos.

Además, la ventaja de usar Symfony Messenger es que permite definir de forma sencilla qué hacer cuando hay algún fallo enviando un mensaje, cuantos reintentos hace, sobre qué protocolo vamos a trabajar, etc.

Si tienes dudas sobre el mundo del intercambio de mensajes, te cuento más teoría en el artículo de Qué son y por qué usar las colas de mensajes.

Por qué deberías usar el Symfony Messenger

Diseñar un sistema mediante eventos o mensajes nos permite desacoplar los diferentes componentes de nuestra aplicación. Considera el siguiente fragmento de código (que es más bien un ejemplo, ya que no respeta los principios SOLID, pero educativamente nos puede servir):

public function createUser(User $user) {
  $userCreator->create($user);
  $mailer->sendWelcomeMail($user);
  $paymentManager->addFreeSubscription($user);
  return new Response("Hemos creado el usuario");
}

Utilizando eventos o mensajes, tan solo debemos lanzar el evento Se ha creado un usuario nuevo y configurar qué clases deberán reaccionar a tal evento. Este ejemplo es mucho más sencillo de leer y el código está completamente desacomplado:

public function index(MessageBusInterface $messageBus): Response  
{  
    $messageBus->dispatch(new UserCreateNotification($user));  
    return new Response("Hemos creado el usuario");  
}

Además, podemos configurar la ejecución de estos procesos de forma síncrona (antes de que termine la petición HTTP) o de forma asíncrona.

Otra de las razones que justifica el uso de Symfony Message es que podemos enviar información en forma de mensajes de forma fácil y sencilla a otros sistemas o plataformas. Vivimos en el mundo de los microservicios y el envío y recepción de mensajes es necesario entre estas plataformas. Una máquina de inteligencia artificial seguramente esté diseñada en Python, una API en Node.js y un sistema de cálculo numérico en Rust o Go. Estos sistemas tienen que compartir información y el mundo de las colas de mensajes es una forma ideal de compartir información de forma asíncrona.

Usando Symfony Messenger con otros sistemas no PHP

A priori, cuando enviamos un mensaje usando el Symfony Messenger vemos que el texto enviado es algo parecido a esto: O:36:\"Symfony\\Component\\Messenger\\Envelope\":2:{s:44:\"\0Symfony\\Component\\Messenger\\Envelope\0stamps\";a:1:{s:46:\"Symfony\\Component\\Messenger\\Stamp\\BusNameStamp\";a:1:{i....... Esto se debe a que el componente Symfony Message emplea por defecto el serializador de PHP, lo que complica el uso de estos mensajes en plataformas no implementadas en PHP.

Sin embargo, ¿cómo podemos configurar Symfony Messenger para comunicarnos con otros sistemas? Bien, podemos definir cómo serializar (y deserializar) estos mensajes de forma sencilla. El objetivo es utilizar el serializador JSON, mucho más estandarizado y disponible en la inmensa mayoría de plataformas.

Para ello, debemos dirigirnos al fichero de configuración config/packages/messenger.yml y añadir la línea serializer:

transports:
  dsn: "%env(MESSENGER_TRANSPORT_DSN)%"  
  serializer: App\Serializer\AsyncMessageSerializer

routing:  
  'App\Message\UserCreationMessage': async

Después, vamos programar el serializador, que se ubica en App\Serializer\AsyncMessageSerializer:

<?php  
  
namespace App\Serializer;  
  
use App\Message\UserCreationMessage;  
use Symfony\Component\Messenger\Envelope;  
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;  
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface as MessageSerializerInterface;  
  
class AsyncMessageSerializer implements MessageSerializerInterface  
{  
    /**  
     * This method will be called when we receive a message from the broker 
     * @return Envelope  
     */
    public function decode(array $encodedEnvelope): Envelope  
    {  
        $body = $encodedEnvelope['body'];  
        $headers = $encodedEnvelope['headers'];  
  
        $data = json_decode($body, true);  
        if (!array_key_exists('content', $data)) {  
            throw new MessageDecodingFailedException('Invalid message format');  
        }  
  
  
        $stamps = null;  
        if (!empty($headers['stamps'])) {  
            $stamps = unserialize($headers['stamps']);  
        }  
  
        return new Envelope(new UserCreationMessage($data['content']), $stamps);  
    }  
  
  
/**  
 * This method will be called when we send a message to the broker 
 * @param Envelope $envelope  
 * @return array  
 * @throws \Exception  
  */  
  public function encode(Envelope $envelope): array  
  {  
    $message = $envelope->getMessage();  
  
    if (!$message instanceof UserCreationMessage) {  
      throw new \Exception(sprintf('Serializer does not support message of type %s.', $message::class));  
    }  
  
    $data = [  
      'content' => $message->getContent(),  
    ];  
  
    $allStamps = array_map(fn($stamps) => array_shift($stamps), $envelope->all());  
  
    return [  
      'body' => json_encode($data),  
      'headers' => [  
         'stamps' => serialize($allStamps)  
      ]  
    ];  
  }  
}

¡ Y listo ! De esta forma, el contenido de los mensajes que salgan de nuestro aplicativo así como lo que entren, pasarán por este serializador. Esto nos permitirá enviar y recibir mensajes desde otros sistemas que no sean PHP.

Conclusión

Las colas de mensajes son un asunto muy importante en el mundo de hoy en día. Cada vez más, los viejos sistemas monolíticos se están deshaciendo para dejar paso al mundo de los microservicios, o por lo menos, pequeñas aplicaciones. Para poder comunicar todos los sistemas que componen una aplicación podemos usar las colas de mensajes para llamadas asíncronas.

Gracias al componente Symfony Messenger, ya no tenemos excusa para no usarlo de forma sencilla, cómoda y robusta en nuestras aplicaciones PHP. Y ahora que sabemos cómo comunicarnos en formato JSON en vez de serializado PHP, ya podemos comunicarnos con cualquier aplicación, esté diseñada en el lenguaje que sea.

Además, la ventaja de Symfony Messenger es que es un componente que puedes utilizar en cualquier proyecto PHP que utilice Composer, por lo que no es necesario el uso total del framework para poder utilizar este componente.

¡ Qué tengas un feliz coding !