Utilizzare Symfony Messenger per gestire code di messaggi in Symfony
Symfony, il famoso framework PHP, offre una vasta gamma di strumenti e componenti potenti per sviluppatori di tutti i livelli. Uno di questi strumenti è Symfony Messenger, un bundle che semplifica notevolmente la gestione dei messaggi all’interno delle applicazioni Symfony.
Cos’è Symfony Messenger?
Symfony Messenger è un bundle che fornisce tutto ciò di cui hai bisogno per consumare i messaggi provenienti da una coda di messaggi. Questo approccio è estremamente utile per migliorare le prestazioni delle nostre applicazioni, consentendo di separare le attività pesanti e gestirle tramite un lavoratore separato.
Vantaggi dell’accodamento dei messaggi
L’accodamento dei messaggi consente di:
- Migliorare le prestazioni dell’applicazione;
- Gestire attività pesanti in modo asincrono;
- Mantenere un tempo di risposta rapido per gli utenti.
Se non hai familiarità con l’accodamento dei messaggi, ti consiglio di approfondire l’argomento per comprendere appieno i suoi benefici.
Caso d’uso
Per comprendere meglio l’utilità di Symfony Messenger, consideriamo un caso d’uso comune: la gestione degli ordini e delle spedizioni in un’applicazione.
Immaginiamo di dover eseguire diverse attività ogni volta che un ordine viene aggiornato, come:
- Aggiornare lo stato dell’ordine sul sito web;
- Inviare una e-mail di conferma all’utente;
- Magari, inviare una notifica tramite SMS;
- Eseguire altri script interni.
Tutte queste attività richiedono chiamate a servizi diversi, il che può richiedere molto tempo e il nostro obiettivo è avere il minor tempo di risposta possibile.
Utilizzando Symfony Messenger, potremmo delegare queste attività a un lavoratore separato, migliorando notevolmente le prestazioni complessive dell’applicazione.
Introduzione e installazione
Per iniziare a utilizzare Symfony Messenger, dobbiamo prima installare il pacchetto tramite Composer:
> composer require symfony/messenger
Questo pacchetto fornisce tutto il necessario per creare e gestire messaggi e gestori di messaggi all’interno della nostra applicazione.
Messaggi e Gestori
Il cuore di Symfony Messenger ruota attorno ai messaggi e ai gestori di messaggi. Un messaggio rappresenta i dati che devono essere elaborati, mentre un gestore di messaggi contiene la logica per elaborare quel messaggio.
Ad esempio, supponiamo di dover inviare un’e-mail di conferma a un utente che si è appena registrato sul nostro sito. Creeremo un messaggio chiamato SendRegistrationEmailMessage
che conterrà l’ID dell’utente.
<?php
namespace App\Message;
class SendRegistrationEmailMessage
{
public function __construct(
private readonly int $userId
) {}
public function getUserId(): int
{
return $this->userId;
}
}
Invece di eseguire tutto nel momento esatto in cui viene inviata la richiesta di registrazione, invieremo un messaggio in coda, contenente l’ID dell’utente.
In questo modo, il nostro controller non avrà più motivo di lanciare un’eccezione e potremmo inviare una risposta “OK” (200) molto più velocemente.
Successivamente, creeremo un gestore di messaggi chiamato SendRegistraionEmailMessageHandler
che recupererà l’utente dall’ID e invierà l’e-mail di conferma.
<?php
namespace App\MessageHandler;
use App\Entity\User;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class SendRegistrationEmailMessageHandler
{
public function __construct(
private readonly EntityManagerInterface $entityManager
) {}
public function __invoke(SendRegistrationEmailMessage $message): void
{
$user = $this->entityManager->getRepository(User::class)->find($message->getUserId());
if (!$user instanceOf User) {
// Handle entity not found case
}
// Send the email
}
}
Symfony Messenger semplifica questo processo, consentendoci di concentrarci sulla logica di business senza preoccuparci dei dettagli di implementazione.
Vediamo come.
Symfony Messenger viene fornito con un attributo PHP #[AsMessageHandler]
, in modo che Symfony consideri questo servizio come un gestore di messaggi a cui instradare i messaggi corretti.
Il tipo del messaggio da consumare è quello dichiarato nel metodo __invoke
.
Ciò significa che per ogni messaggio, dovremmo creare un gestore di messaggi, con il tipo corretto nel metodo __invoke
e l’instradamento sarà gestito automaticamente da Symfony.
Trasporto e instradamento dei messaggi
Symfony Messenger supporta diversi tipi di trasporto:
- Servizi AMQP (come RabbitMQ);
- Doctrine (memorizzazione dei messaggi in una tabella SQL);
- Servizi di cache (come Redis).
Innanzitutto, se intendiamo utilizzare un protocollo AMQP, dobbiamo installare il pacchetto:
> composer require symfony/amqp-messenger
Se intendiamo utilizzare Doctrine, invece bisogna installare quanto segue:
> composer require symfony/doctrine-messenger
Infine, se desideriamo utilizzare Redis come nostro trasporto, questo è il pacchetto che dobbiamo installare:
> composere require symfony/redis-messenger
Se vuoi saperne di più sugli altri trasporti, puoi dare uno sguardo alla relativa documentazione.
Possiamo configurare facilmente il trasporto desiderato nel file di configurazione del pacchetto messenger.yaml
, che troveremo in config/packages
insieme a tutti gli altri file di configurazione dei pacchetti installati.
La chiave transport
contiene tutta la configurazione riguardante la gestione (consumo) dei messaggi.
La chiave failure-transport
contiene il nome del trasporto che deve essere utilizzato in caso di problemi (eccezione lanciata durante la gestione).
Se qualcosa si blocca qui, l’analisi dello stack e i dettagli dell’eccezione verranno salvati nel messaggio e potranno essere recuperati in seguito per essere gestiti correttamente.
Possiamo anche configurare il consumatore per eseguire x tentativi prima di inviare un messaggio di errore sulla coda “non riuscita” e gestire priorità diverse per ciascuna coda.
Infine, la parte routing
spiega come instradare una determinata istanza del messaggio al trasporto.
Ecco un esempio di come potrebbe essere il nostro file di configurazione del caso riportato sopra:
framework:
messenger:
transports:
registration_email:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
failure_transport: registration_email_failed
retry_strategy:
max_retries: 3
delay: 1000
multiplier: 2
max_delay: 0
options:
exchange:
name: registration_email
queues:
registration_email: ~
registration_email_failed:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
exchange:
name: registration_email_failed
queues:
registration_email_failed: ~
routing:
'App\Message\SendRegistrationEmailMessage': registration_email
Pubblicazione di messaggi
La pubblicazione di un messaggio è semplice come chiamare il servizio MessageBus
fornito da Symfony Messenger e passare il messaggio da pubblicare. Il bus si occuperà del resto, instradando il messaggio al gestore appropriato per l’elaborazione.
Ecco un esempio:
<?php
namespace App\Service;
use App\Message\SendEMailRegistrationEmailMessage;
class MyExampleService
{
public function __construct(
private readonly MessageBusInterface $messageBus
) {}
public function doSomething(): void
{
// Any logic...
$message = new SendEMailRegistrationEmailMessage($userId);
# This will dispatch the message to the correct transport
$this->messageBus->dispatch($message);
}
}
Architettura
L’architettura di Symfony Messenger è ben strutturata e prevede l’uso di un editore (controller, servizio, comando, ecc.) che invia un messaggio al bus. Se il bus è sincrono, il messaggio viene consumato direttamente da un gestore. Se il bus è asincrono, il messaggio viene inviato tramite un trasporto a un sistema di code, dove viene elaborato da un lavoratore separato.
Se il trasporto è asincrono, il messaggio dovrà essere serializzato per poter essere interpretato e consumato correttamente dal lavoratore. Symfony supporta nativamente due modalità di serializzazione:
- Serializzazione nativa di PHP.
- Serializzazione tramite il componente Serializer.
Di default viene utilizzata la serializzazione nativa di PHP, che rappresenta la classe stessa del messaggio.
Se un’altra applicazione consuma la stessa coda di messaggi, potrebbe non avere questa classe, quindi sarà impossibile deserializzare il messaggio. Per tale motivo, utilizzeremo un formato di scambio più tradizionale. Di solito JSON, ma potremmo utilizzare XML, Protobuf o qualsiasi altro linguaggio di serializzazione interoperabile.
Conclusioni
Symfony Messenger è uno strumento potente per la gestione dei messaggi all’interno delle applicazioni Symfony. Con il suo supporto per vari tipi di trasporto e la sua semplice architettura, ci consente di migliorare le prestazioni dell’applicazione e fornire un’esperienza utente migliore.
Spero che questo articolo ti abbia fornito una panoramica completa di Symfony Messenger e ti abbia ispirato ad utilizzarlo nelle tue prossime applicazioni Symfony! Se desideri ricevere aggiornamenti su futuri articoli, non esitare a seguire il mio account su Medium!
Buon lavoro 👨💻