Комментарии 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, которую я решил поделить. Во второй части я подробно на примере покажу, как работает транзакционный обмен сообщениями.

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