Обновить

События vs сообщения. Понимаете ли вы разницу и почему это важно?

Уровень сложностиПростой
Время на прочтение10 мин
Охват и читатели14K
Всего голосов 22: ↑19 и ↓3+21
Комментарии7

Комментарии 7

Спасибо, @slayervc за поднятие темы. Согласен, что сообщение это не событие, а Concurrency is not parallelism, да и вообще странно видеть два этих понятия в одном сравнении. Сообщение шлем кому-то о чем то. Событие "бросаем", и о получении речи не идет.

Другой вопрос - разговорная практика. Кто-то надевает рубашку, кто-то одевает, да, один филолог умер в таком примере, но, в любом случае, о чем идет речь - понятно.

Так и тут: я могу отправить сообщение о том, что произошло событие и могу включить атрибуты события в состав message. Или могу сказать "отправить событие", да еще один филолог умер, но суть понятна. я отправил сообщение с событием в теле. И это понятно и быстро иии .... в статье именно эта устная форма и используется:

Основная мысль здесь состоит в том, что вы сначала определяетесь с тем, что вы отправляете (событие, документ, команду)

И если душнить, то вообще-то отправляем мы сообщение. В сообщение положим не событие, а тело объекта события и т.д. Но даже в тексте проще было сказать: "отправляем событие". И всем всё ясно... Или нет.

Короче, это почти как в том анекдоте:

Учительница: — Я уже сотый раз объясняю, что половина не может быть большей или меньшей! А большая половина класса этого так и не понимает!.

Спасибо за Ваш комментарий.) Я потому и поставил уровень статьи "легкий".) Если человек понимает разницу, то ему статья с объяснением этой разницы будет бесполезна.) Но беда в том, что я встречал очень многих разработчиков, которые этой разницы не понимают, и это воплощалось в их коде.

Возможно, я слишком большой акцент сделал на различии понятий и слишком малый - на архитектуре. Идея-то была показать, где, в каком слое, в проекте с хорошей архитектурой место события, где - его обработчиков, а где - транспорта.

Основная мысль, которая, возможно, утонула в лингвистически разборах, о которых Вы говорите, состояла в том, что доменное событие, прежде чем дойти до внешнего консьюмера, проходит длинный путь преобразования: Доменное событие -> Domain (!) Event Dispatcher -> Обработчик -> Message (перзистентное) -> Relay -> сериалайзер Symfony Messenger -> Транспорт Symfony Messenger -> Rabbit MQ и далее в консьюмере обратный путь через десереализацию к хандлеру, который уже совсем не доменный.

Это, на самом деле, первая часть большой статьи, которую я решил разделить. Спасибо, что подсветили, во второй части сделаю прям больший упор на архитектуру, код и путь движения события через слои.

Но беда в том, что я встречал очень многих разработчиков

К сожалению это происходит очень часто. В итоге люди путают семантику этих двух понятий и в следствии этого порождаются очень странные архитектурные решение, происходит сильное кросс сервисное/контекстное зацепление (где его не должно быть) и тд.

Мне кажется важно запомнить / понимать базовые отличия, чтобы успешно оперировать событиями и сообщениями (job-ами) в архитектуре приложения.

Событие

  • Говорит о том что УЖЕ случилось в прошлом

  • Паблишер событий НИКОГДА не знает о обработчиках событий

  • Обработчиков может быть от 0 до бесконечности

  • Как правило служит для организации кросс контекстного / сервисного взаимодействия


Сообщение (job-а)

  • Служит обычным средством распараллеливания / асинхронного выполнения тех или иных операций в приложении

  • Постановка задачи в будущем

  • Паблишер как правило знает о обработчике событий

  • Обработчик как правило один

Ну и да, событие - это дефакто сообщение в шине, но имеющее иную смысловую нагрузку нежели просто job-а, а также событие и job-а не должны пересекаться в одном транспорте.

НЛО прилетело и опубликовало эту надпись здесь

Теперь мы можем писать различные обработчики для события UserCreated. Например, обработчик отправляющий в telegram приветствие нового пользователя

В примере событие брошено в конструкторе объекта, при таком сценарии мы отправим приветствие в телеграм, даже если пользователя не удалось сохранить в базу данных.

Как выглядит обработка таких сценариев?

То есть в теории это красиво и логично, но на практике событие "юзер создан" это всё-таки его успешное сохрание куда-то, а не конструкция объекта.

Очень хороший вопрос. Вам нужен паттерн "Транзакционный обмен сообщениями" Криса Ричардсона https://microservices.io/patterns/data/transactional-outbox.html.

Коротко: на все ваши события подписан обработчик, который сохраняет их в базу. В Application слое вызов бизнес-логики, бросающей события, обернут в одну транзакцию с вызовом метода save() репозитория. Так вы гарантируете атомарность выполнения действия и выброса события. Отдельный класс `Message Relay` вытаскивает сохраненные события из базы и дальше оборачивает их в сообщение и отправляет асинхронный транспорт (тот же раббит). Уже консьюмер сообщения рабита вытаскивает из него событие и асинхронно обрабатывает его.

Эта статья - первая часть большой статьи про Messenger, которую я решил поделить. Во второй части я подробно на примере покажу, как работает транзакционный обмен сообщениями.

Ждем продолжения

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации