Dzisiejsze aplikacje wymagają coraz większej elastyczności i zdolności do radzenia sobie z dużą ilością danych, co często wymaga komunikacji asynchronicznej między różnymi komponentami. W tym kontekście, RabbitMQ i Symfony Messenger są idealnymi narzędziami do tworzenia architektury opartej na zdarzeniach i zarządzania komunikatami. W tym artykule przyjrzymy się, jak krok po kroku wykorzystać RabbitMQ oraz Symfony Messenger, by efektywnie wdrożyć asynchroniczną obsługę wiadomości.
Architektura Zdarzeń i Symfony Messenger
Symfony Messenger to komponent pozwalający na łatwą implementację wzorca wiadomości w Symfony. Można go używać do:
- Wysyłania wiadomości do innych części aplikacji.
- Obsługi zdarzeń i kolejkowania zadań.
- Integracji z brokerami komunikatów, takimi jak RabbitMQ.
Krok 1: Instalacja Symfony Messenger i RabbitMQ
Na początek należy zainstalować potrzebne zależności. Symfony Messenger integruje się z RabbitMQ za pomocą symfony/amqp-messenger. Instalacja przebiega następująco:
composer require symfony/messenger
composer require enqueue/amqp-bunny
Krok 2: Konfiguracja Symfony Messenger z RabbitMQ
Skonfiguruj Messenger w pliku config/packages/messenger.yaml:
framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
exchange:
name: 'messages'
routing:
'App\Message\EmailNotification': async
Konfiguracja ta oznacza, że wiadomości typu EmailNotification będą przesyłane do kolejki async, która jest podłączona do RabbitMQ. Aby zdefiniować dsn, należy dodać wpis do .env:
MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
W tym wpisie guest:guest to domyślne poświadczenia RabbitMQ, a localhost:5672 to miejsce, gdzie działa RabbitMQ.
Krok 3: Tworzenie Klasy Wiadomości
Klasa wiadomości definiuje dane, które mają zostać przesłane. Tworzymy przykładową klasę EmailNotification, której zadaniem jest zawarcie danych o wiadomości e-mail do wysłania:
namespace App\Message;
class EmailNotification
{
private string $email;
public function __construct(string $email)
{
$this->email = $email;
}
public function getEmail(): string
{
return $this->email;
}
}
W klasie EmailNotification mamy prywatne pole $email i metodę getEmail() do jego pobrania. Ta klasa jest prostym sposobem na przenoszenie danych między komponentami.
Krok 4: Wysłanie Komunikatu
W Symfony możemy użyć Messenger do wysłania komunikatu za pomocą MessageBusInterface. Oto jak to zrobić:
namespace App\Controller;
use App\Message\EmailNotification;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
class RegistrationController extends AbstractController
{
private MessageBusInterface $bus;
public function __construct(MessageBusInterface $bus)
{
$this->bus = $bus;
}
/**
* @Route("/register", name="register_user")
*/
public function register(): Response
{
// Przykład logiki rejestracji użytkownika
// Po zarejestrowaniu użytkownika wysyłamy wiadomość do kolejki
$this->bus->dispatch(new EmailNotification('user@example.com'));
return new Response('User registered and email notification dispatched');
}
}
Tutaj:
- Używamy
MessageBusInterfacedodispatchwiadomościEmailNotificationpo zarejestrowaniu użytkownika. - Metoda
dispatch()dodaje komunikat do kolejki RabbitMQ.
Krok 5: Tworzenie Handlera Wiadomości
Handler to klasa, która przetwarza wiadomości przesłane przez Messenger. Stwórzmy EmailNotificationHandler, który odpowiada za obsługę komunikatu i wysłanie e-maila:
namespace App\MessageHandler;
use App\Message\EmailNotification;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class EmailNotificationHandler implements MessageHandlerInterface
{
public function __invoke(EmailNotification $notification)
{
// Przykład logiki wysyłania e-maila
$email = $notification->getEmail();
// Wyobraźmy sobie, że mamy jakąś usługę MailerService
mail($email, 'Welcome!', 'Thank you for registering with us.');
echo "Email sent to: " . $email . "\n";
}
}
Handler jest bardzo prosty:
- Metoda
__invoke()odbiera wiadomośćEmailNotification. - Zawiera podstawową logikę wysyłania wiadomości e-mail.
Krok 6: Opcje Retry, Delay i Redelivery
Symfony Messenger pozwala na konfigurację opcji takich jak retry, delay i redelivery. To kluczowe aspekty, które zwiększają niezawodność systemu.
Retry (Ponowienie Prób)
Jeśli przetworzenie wiadomości nie powiedzie się, Symfony może automatycznie próbować ponownie przetworzyć komunikat. Konfiguracja może wyglądać tak:
framework:
messenger:
failure_transport: failed
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 5
delay: 1000
multiplier: 2
max_delay: 10000
- max_retries: Maksymalna liczba prób przetworzenia wiadomości.
- delay: Początkowe opóźnienie w milisekundach przed ponowną próbą.
- multiplier: Każde kolejne opóźnienie jest zwiększane według tego współczynnika.
- max_delay: Maksymalny czas opóźnienia pomiędzy ponownymi próbami.
Delay (Opóźnienie)
Możemy skonfigurować, aby wiadomość została przetworzona z pewnym opóźnieniem:
use Symfony\Component\Messenger\Stamp\DelayStamp;
$this->bus->dispatch(new EmailNotification('user@example.com'), [
new DelayStamp(5000) // Opóźnienie 5 sekund
]);
W ten sposób możemy opóźnić przetworzenie wiadomości o 5000 milisekund (5 sekund).
Redelivery (Przekierowanie do Kolejki Niepowodzeń)
Jeśli wiadomości nie można przetworzyć, możemy przekierować ją do osobnej kolejki, tzw. failure transport:
framework:
messenger:
failure_transport: failed
transports:
failed: 'doctrine://default?queue_name=failed'
W przypadku niepowodzenia, wiadomości trafią do kolejki failed, co pozwala na ich późniejsze przeanalizowanie i ewentualne przetworzenie.
Podsumowanie
RabbitMQ i Symfony Messenger to potężne narzędzia do tworzenia asynchronicznej komunikacji opartej na zdarzeniach. Dzięki klasom wiadomości, wysyłaniu komunikatów i odpowiednim handlerom, aplikacje stają się skalowalne, elastyczne i odporne na błędy. Opcje takie jak retry, delay i redelivery dodatkowo zwiększają niezawodność aplikacji, co sprawia, że Messenger idealnie sprawdza się w złożonych systemach.
Stosując takie podejście, zyskujemy:
- Odporność na awarie: Nawet jeśli przetwarzanie się nie uda, system będzie próbować ponownie.
- Skalowalność: Możliwość rozpraszania zadań i delegowania ich do wielu handlerów.
- Elastyczność: Komunikaty można łatwo rozbudowywać, dodając nowe rodzaje wiadomości i handlerów.
Zachęcam do eksperymentowania z Messengerem w Symfony, aby zobaczyć, jak prosto można osiągnąć wydajną i skalowalną architekturę opartą na zdarzeniach.