Size: a a a

Software Design/Architecture/Zen

2020 December 27

ЕР

Евгений Ромашкан... in Software Design/Architecture/Zen
Vladimir Alexeev
Резюмируя, доменные события позволяют развязать сущность и логику операции или ряда операций, выполняющихся при отправке сообщения этой сущности. Сам доменный объект поддерживает ряд операций, которые меняют (или не меняют) его стейт, и уведомляет заинтересованные обработчики этого события. Так?
Да, только доменный объект просто генерирует события, формально, обработчики его не интересуют, достанет события и закинет в обработчики уже какая-то инфраструктура
источник

SP

Sergey Protko in Software Design/Architecture/Zen
Vladimir Alexeev
Резюмируя, доменные события позволяют развязать сущность и логику операции или ряда операций, выполняющихся при отправке сообщения этой сущности. Сам доменный объект поддерживает ряд операций, которые меняют (или не меняют) его стейт, и уведомляет заинтересованные обработчики этого события. Так?
смысл события сообщить другим мол "что-то произошло". Как правило это либо изменения стэйта или "не удалось выполнить операцию". При этом можно просто для каждой атормарной операции кидать событие а слушает его кто или нет - не важно (в этом смысл событий).
источник

SP

Sergey Protko in Software Design/Architecture/Zen
за счет этого появляется возможность в системе выстраивать политики - мол "если заказ подтвержден - начинаем другой процесс". При этом появляются самодостаточные куски.
источник

VA

Vladimir Alexeev in Software Design/Architecture/Zen
А есть какие-то ограничения по тому, как может быть реализован обработчик события? Может ли он, скажем, включать какие-нибудь IoC зависимости? Например, возвращаясь к примеру с SMS, у меня есть операция над юзером, и при определенном ветвлении в ней я хочу отправить ему SMS. Может ли SomeOpOnUserEventHandler содержать внешнюю зависимость, например, SMSSender?
источник

SP

Sergey Protko in Software Design/Architecture/Zen
Vladimir Alexeev
А есть какие-то ограничения по тому, как может быть реализован обработчик события? Может ли он, скажем, включать какие-нибудь IoC зависимости? Например, возвращаясь к примеру с SMS, у меня есть операция над юзером, и при определенном ветвлении в ней я хочу отправить ему SMS. Может ли SomeOpOnUserEventHandler содержать внешнюю зависимость, например, SMSSender?
если говорить про разумные ограничения, но в целом я рекомендую следующие:

- обработчик должен быть абсолютно независим от других обработчиков того же события. У меня было масса проблем из-за неявных зависимостей мол "один ивент - несколько обработчиков - они внезапно зависят от того кто в каком порядке отработал". Лучше сразу договориться мол "в пределах сообщения порядок обработки не гарантирован и не надо на него завязываться - важен порядок - кидайте новые события или дергаейте операции явно.
- очень опасная штука время, когда обработчики зависят от того когда сообщение дошло. Например есть кейсы когда кинули ивент, после чего стэйт поменялся еще раз. То есть на момент получения события стэйт уже слегка поменялся и могут быть проблемы в плане принятия решений. потому в ситуациях когда "это норма" и нужны всякие там Саги которые не зависят от внешнего стэйта и свой выстраивают только по событиям которые к ним приходят.
- Обработчики желательно делать атомарными. Если тебе надо сходить во внешнюю систему и потом в базу результат записать - лучше делать это по отдельности. Ивент - сходили во внешнюю сситему - кинули ивент который содержит результат - запустили другую операцию которая уже в базу пишет. Однако тут вопрос в инфраструктуре. Если у тебя инфраструктура может ивенты терять то в целом "не так важно это".

Большинство ограничений в целом будут проистекать из того как у тебя инфраструктура организована. У меня например мы делали "аля nservicebus" и там компитинг консюмеры (нет гарантий порядка), возможность гарантий того что сообщение будет сохранено и доставлено но ценой того что "иногда можем доставить не один раз", что вынуждает делать операции идемпотентными. Есть кейсы когда обработчики отрабатывают без ретраев в одном процессе - тогда важно что бы один обработчик не сломал всю цепочку. Есть кейсы когда каждый обработчик работает изолировано + ретраи которые "чинят" кейсы с out-of-order доставкой сообщений (случается в случае компитинг консюмеров). Но в некоторых ситуациях можно скажем юзать консистентные хэши для роутинга и за счет этого гарантировать очередность сообщений там где это важно.

Вариантов масса. Все упирается в инфраструктуру для месседжинга и какие гарантии оно тебе предоставляет.
источник

SP

Sergey Protko in Software Design/Architecture/Zen
что до "можно ли в хэндлерах зависимости" - конечно можно. В общем случае. Если это не сага.

Более того - никто не мешает какой-нибудь "сервис отправки SMS" воспринимать как агрегат)

type SendSMS = {
   type: "SendSMS"
   userId: string
   message: string
}

function sendSmsToUser(command: SendSMS, context: MessageContext) {
   const phoneNumber = contacts.forUser(command.userId)
   const trackingId = twillio.sendSms(phoneNumber, command.message)
   
   context.send(smsMessageSent(command.userId, trackingId))
}
источник

SP

Sergey Protko in Software Design/Architecture/Zen
в этом случае наш "сендер" есть просто хэндлер сообщений. Он общается со своими зависимостями и он. хоть и не является полноценным доменным объетом, может отправлять сообщения наружу.

Мол для тех кто сообщения отправляет и принимает не важно что внутри у хэндлера и как оно работает. Важно какие гарантии оно предоставляет и важно какие сообщения в каких ситуациях чего значат. Тупая процедура эта которая кидает ивенты, или агрегат, или "сущность" - не важно.
источник

SP

Sergey Protko in Software Design/Architecture/Zen
@oh_noes_ma_cookies я все видел - если совсем нихуя не понятно значит вопрос где-то в другом месте. Это может быть "нахер это все надо". или "а где профит".

Профит тут может быть что например трекинг доставки можно вынести как независимый модуль который подписан на SmsMessageSent и сам свой стэйт хэндлит. А тот кто просто хотел отправить sms-ку - ему хочется только "сообщение отправлено и сообщение прочитано" ивенты. А что между ними - детали которые знать не надо
источник

SP

Sergey Protko in Software Design/Architecture/Zen
а да еще важный момент - лучше сразу договориться о разделении ивентов - мол просто доменные ивенты и "интеграционные ивенты", которые мы выпускаем за пределы модуля для интеграции фич. В такие ивенты желательно поменьше данных класть - мол "заказ подтвержден", может быть время когда и айдишка заказа. Кто кем и т.д. - не ваше дело. Если вам любопытно - сходите по api и заберите.
источник

SP

Sergey Protko in Software Design/Architecture/Zen
а то очень быстро начинается что знание о том что значат ивенты размазывается по системе, часто с дублированием. Ну и "менять" потом больно...
источник

VA

Vladimir Alexeev in Software Design/Architecture/Zen
Sergey Protko
что до "можно ли в хэндлерах зависимости" - конечно можно. В общем случае. Если это не сага.

Более того - никто не мешает какой-нибудь "сервис отправки SMS" воспринимать как агрегат)

type SendSMS = {
   type: "SendSMS"
   userId: string
   message: string
}

function sendSmsToUser(command: SendSMS, context: MessageContext) {
   const phoneNumber = contacts.forUser(command.userId)
   const trackingId = twillio.sendSms(phoneNumber, command.message)
   
   context.send(smsMessageSent(command.userId, trackingId))
}
А sendSmsToUser - это контроллер или домен?
источник

SP

Sergey Protko in Software Design/Architecture/Zen
Vladimir Alexeev
А sendSmsToUser - это контроллер или домен?
это хэндлер)
источник

SP

Sergey Protko in Software Design/Architecture/Zen
но это точно не домен. Это скорее инфраструктура
источник

SP

Sergey Protko in Software Design/Architecture/Zen
я к тому что событиями может не только домен плеваться
источник

VA

Vladimir Alexeev in Software Design/Architecture/Zen
Sergey Protko
это хэндлер)
А если типизация статическая (e.g java), как реализовать логику непосредственно вызова события?

List<Handler<? extends Event>> handlers;
...
this.fire(new Event<SomeType>)
...

Как будет реализован fire, т.е передача уведомления обработчика <? extends Event>) событием типа SomeType extends Event, если у нас дженерик в листе handlers стирается и мы не знаем, какой тип событий обрабатывает хендлер?
источник

SP

Sergey Protko in Software Design/Architecture/Zen
Vladimir Alexeev
А если типизация статическая (e.g java), как реализовать логику непосредственно вызова события?

List<Handler<? extends Event>> handlers;
...
this.fire(new Event<SomeType>)
...

Как будет реализован fire, т.е передача уведомления обработчика <? extends Event>) событием типа SomeType extends Event, если у нас дженерик в листе handlers стирается и мы не знаем, какой тип событий обрабатывает хендлер?
погугли как эту задачу решают другие. Есть куча готовых инфраструктур под это дело.
источник

VA

Vladimir Alexeev in Software Design/Architecture/Zen
Sergey Protko
погугли как эту задачу решают другие. Есть куча готовых инфраструктур под это дело.
Ок, спасибо
источник

SP

Sergey Protko in Software Design/Architecture/Zen
я плохо знаю что есть в java под это дело - можно попробовать глянуть Axon Framework.
источник

SP

Sergey Protko in Software Design/Architecture/Zen
но уверен есть куда более приятные и простые альтернативы
источник

SP

Sergey Protko in Software Design/Architecture/Zen
будет очень странно если под c# есть десяток разных вариантов а под джаву только Axon.
источник