Компонент Messenger
Дата оновлення перекладу 2024-05-03
Компонент Messenger
Компонент Messenger допомагає додаткам надсилати та приймати повідомлення з інших додатків або через черги повідомлень.
Компонент багато в чому було засновано на серії постів Маттіаса Нобака про автобуси команд та проект SimpleBus.
See also
Ця стаття пояснює, як використовувати функції Messenger в якості незалежного компонента у будь-якому додатку PHP. Прочитайте статтю Messenger: робота з синхронізованими повідомленнями та повідомленнями у черзі, щоб дізнатися про те, як використовувати його у додатках Symfony.
Установка
1
$ composer require symfony/messenger
Note
Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити
файл vendor/autoload.php
у вашому коді для включення механізму автозавантаження
класів, наданих Composer. Детальніше можна прочитати у цій статті.
Концепції
- Відправник:
- Відповідає за серіалізацію та відправку повідомлень чомусь. Це щось може бути брокером повідомлнень або стороннім API, наприклад.
- Отримувач:
- Відповідає за десеріалізацію та перенаправлення повідомлень обробнику(ам). Це може бути пулер черги повідомлень або кінцева точка API, наприклад.
- Обробник:
-
Відповідає за обробку повідомлень, використовуючи бізнес-логіку, що можна застосувати
до повідомлень. Обробники викликаються проміжковим ПЗ
HandleMessageMiddleware
. - Проміжкове ПЗ:
- Проміжкове ПЗ може отримати доступ до повідолення і його обгортки (конверту) під час запуску через автобус. В буквальному сенсі "програмне забезпечення посередині", воно не стосується основної функціональності (бізнес-логіки) додатку. Замість цього, воно скорочує шлях та може застосовуватися по всьому додатку, впливаючи на весь автобус повідомлень. Наприклад: ведення логів, валідацію повідомлення, початок транзакції, ... Воно також відповідальне за виклик наступного проміжкового ПЗ в ланцюжку, що означає, що воно також може налаштовувати конверт, додаючи до нього штампи або навіть переміщуючи його, а також перервати ланцюжок проміжкового ПЗ. Проміжкове ПЗ викликається при початковому оголошенні повідомлення, і пізніше, коли повідомлення отримано від транспорту.
- Конверт:
- Спеціальна концепнція месенджеру, що надає повну гнучкість всередині автобусу з повідомленнями, обгортаючи в себе повідомлення, та дозволяюючи додавати корисну інформацію всередині шляхом марок на конвертах.
- Штампи на конвертах:
- Інформація, яку вам потрібно додати до повідомлення: контекст серіалізатору, який використовувати для транспорту, маркери, що вказують на отримання повідомлення, або будь-які інші метадані, які може використовувати ваше проміжкове ПЗ або шар транспорту.
Автобус
Автобус використовується для оголошення повідомлень. Поведінка автобусу впорядкована в сполучному стеці. Компонент постачається з набором сполучного ПЗ, яке ви можете використовувати.
При використанні автобусу повідомлень з пакетом Symfony FrameworkBundle, для вас конфігурується наступне проміжкове ПЗ:
- SendMessageMiddleware (підключає асинхронну обробку, веде логи обробки ваших повідомлень, якщо ви передасте логер)
- HandleMessageMiddleware (викликає зареєстрованого(их) обробника(ів))
Приклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use App\Message\MyMessage;
use App\MessageHandler\MyMessageHandler;
use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
$handler = new MyMessageHandler();
$bus = new MessageBus([
new HandleMessageMiddleware(new HandlersLocator([
MyMessage::class => [$handler],
])),
]);
$bus->dispatch(new MyMessage(/* ... */));
Note
Кожне проміжкове ПЗ має реалізовувати MiddlewareInterface.
Обробники
Після диспетчерізації в автобус, повідомлення будуть оброблені "обробниками повідомлень". Обробник повідомлень - це PHP-викликане (тобто функція або екземпляр класу), яке буде проводити необхідну обробку для вашого повідомлення:
1 2 3 4 5 6 7 8 9 10 11
namespace App\MessageHandler;
use App\Message\MyMessage;
class MyMessageHandler
{
public function __invoke(MyMessage $message): void
{
// Обработка сообщения...
}
}
Додавання метаданих до повідомлення (конверти)
Якщо вам необхідно додати метадані або деяку конфігурацію в повідомлення, обгорніть
його в клас Envelope та додайте штампи. Наприклад,
щоб встановити групи серіалізації, що використовуються при проходженні повідомлення через
шар транспорту, використовуйте штамп SerializerStamp
:
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Stamp\SerializerStamp;
$bus->dispatch(
(new Envelope($message))->with(new SerializerStamp([
// групи застосовується до всього повідомлення, тому не забудьте
// визначити групу для кожного вбудованого об'єкту
'groups' => ['my_serialization_groups'],
]))
);
Ось деякі важливі штампки на конверти, які відправляються в Symfony Messenger:
- DelayStamp,
- щоб відкласти обробку асинхронного повідомлення.
- DispatchAfterCurrentBusStamp,
- щоб повідомлення було оброблено після того, як буде виконаний поточний автобус. Прочитайте більше в Транзакційні повідомлення: обробляйте повідомлення після того, як обробку завершено.
- HandledStamp,
- штамп, який відмічає повідомлення обробленими конкретним обробником. Дозволяє отримати доступ до значення, що повернено обробником, та назві обробнику.
- ReceivedStamp,
- внутрішній штамп, який відмічає повідомлення отриманим від транспорту.
- SentStamp,
- штамп, який відмічає повідомлення відправленим від конкретного відправника. Дозволяє отримати доступ до FQCN відправника та псевдонім, якщо він доступний, з SendersLocator.
- SerializerStamp,
- щоб сконфігурувати групи серіалізації, що використовуються транспортом.
- ValidationStamp,
- щоб сконфігурувати групи валідації, що використовуються при увімкненнні проміжкового ПЗ валідації.
- ErrorDetailsStamp,
- внутрішній штамп, коли повідомлення зазнає невдачі у звʼязку з виключенням в обробнику.
- ScheduledStamp, штамп, який позначає повідомлення як створене планувальником. Це допомагає відрізнити його від повідомлень, створених "вручну". Ви можете дізнатися більше про це у Документації про планувальник.
Note
Штамп ErrorDetailsStamp містить FlattenException, який є представленням виключення, що спричинив помилку у повідомленні. Ви можете отримати це виключення за допомогою метода getFlattenException(). Це виключення нормалізовано завдяки FlattenExceptionNormalizer, що полегшує звітування про помилки у контексті Messenger.
Замість того, щоб розбиратися з повідомленнями напряму в проміжковому ПЗ, ви отримуєте конверт. Таким чином, ви можете досліджувати зміст конверту та його штампу, або додати їх:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
use App\Message\Stamp\AnotherStamp;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
class MyOwnMiddleware implements MiddlewareInterface
{
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
if (null !== $envelope->last(ReceivedStamp::class)) {
// Повідомлення було щойно отримане...
// Ви можете, наприклад, додати інший штамп.
$envelope = $envelope->with(new AnotherStamp(/* ... */));
} else {
// Повідомлення було щойно оголошено
}
return $stack->next()->handle($envelope, $stack);
}
}
Приклад вище перенаправить повідомлення до наступного проміжкового ПЗ з додатковим
штампом якщо повідомлення було щойно отримане (тобто, має хоча б один штамп
ReceivedStamp
). Ви можете створювати власні штампи, реалізуючи
StampInterface.
Якщо ви хочете дослідити всі штампи на конверті, використовуйте метод $envelope->all()
,
який повертає всі штампи, згруповані за типами (FQCN). Як варіант, ви можете ітерувати всі
штампи певного типу, використовуючи FQCN в якості першого параметру цього методу (наприклад,
$envelope->all(ReceivedStamp::class)
).
Note
Будь-який штамп повинен мати можливість бути серіалізованим з використанням компоненту Symfony Серіалізатор, якщо він проходить через транспорт, використовуючи базовий серіалізатор Serializer.
Транспорт
Для відправки та отримання повідомлень вам знадобиться сконфігурувати транспорт. Транспорт відповідатиме за комунікацію з вашим брокером повідомлень або третіми сторонами.
Ваш власний відправник
Використовуючи SenderInterface
ви можете з легкістю створити вашого власного
відправника повідомлень. Уявіть, що у вас вже є повідомлення ImportantAction
,
яке проходить через автобус повідомлень та обробляється обробником. Тепер, вам також
потрібно надіслати це повідомлення у вигляді електронного листа (використовуючи
компоненти Mime та Mailer).
Використовуючи SenderInterface, ви можете створити власного відправника:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
namespace App\MessageSender;
use App\Message\ImportantAction;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
use Symfony\Component\Mime\Email;
class ImportantActionToEmailSender implements SenderInterface
{
public function __construct(
private MailerInterface $mailer,
private string $toEmail,
) {
}
public function send(Envelope $envelope): Envelope
{
$message = $envelope->getMessage();
if (!$message instanceof ImportantAction) {
throw new \InvalidArgumentException(sprintf('This transport only supports "%s" messages.', ImportantAction::class));
}
$this->mailer->send(
(new Email())
->to($this->toEmail)
->subject('Important action made')
->html('<h1>Important action</h1><p>Made by '.$message->getUsername().'</p>')
);
return $envelope;
}
}
Ваш власний отримувач
Отримувач відповідає за отримання повідомлень з джерела та їх диспетчерізацію додатку.
Уявіть, що ви вже обробили якісь "команди" у вашому додатку, використовуючи
повідомлення NewOrder
. Тепер ви хочете інтегруватися з третьою стороною
або успадкованим додатком, але ви не можете використовувати API, і вам потрібно
використовувати загальнодоступний CSV з новими командами.
Ви прочитаєте цей CSV-файл та оголосите повідомлення NewOrder
. Все, що вам
необхідно зробити - це написати ваш користувацький CSV отримувач:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
namespace App\MessageReceiver;
use App\Message\NewOrder;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
use Symfony\Component\Serializer\SerializerInterface;
class NewOrdersFromCsvFileReceiver implements ReceiverInterface
{
public function __construct(
private SerializerInterface $serializer,
private string $filePath,
) {
}
public function get(): iterable
{
// Отримайте конверт у відповідності до вашого транспорту, у більшості випадків,
// ($yourEnvelope тут), використання зв'язку є найпростішим рішенням
if (null === $yourEnvelope) {
return [];
}
try {
$envelope = $this->serializer->decode([
'body' => $yourEnvelope['body'],
'headers' => $yourEnvelope['headers'],
]);
} catch (MessageDecodingFailedException $exception) {
$this->connection->reject($yourEnvelope['id']);
throw $exception;
}
return [$envelope->with(new CustomStamp($yourEnvelope['id']))];
}
public function ack(Envelope $envelope): void
{
// Додайте інформацію про оброблене повідомлення
}
public function reject(Envelope $envelope): void
{
// У випадку користувацього з'єднання
$this->connection->reject($this->findCustomStamp($envelope)->getId());
}
}
Отримувач та відправник в одному автобусі
Щоб дозволити відправку та отримання повідомлень в одному і тому ж автобусі, та уникнути нескінченного циклу, автобус повідомлень додасть до конвертів повідомлень штамп ReceivedStamp, і проміжкове ПЗ SendMessageMiddleware буде знати, що воно не має знову надсилати ці поводомлення у транспорт.