Комментарии 57
Фронтенд всегда был event-driven.
В jQuery можно было пулять события. Пуляй - не хочу.
Фреймворк Backbone в принципе весь был построен вокруг эвентов и подписок на них.
Вы правы, события во фронтенде действительно существуют со времён jQuery. Но есть нюанс: в те годы мы говорили о событиях DOM — кликах, ховерах, сабмитах. Это как общение через таблички «Нажми меня» — локально и прямолинейно.
Современные SPA с их компонентной архитектурой — это уже не кнопки на странице, а целые экосистемы.
В jQuery вы вешаете обработчик на элемент → жёсткая привязка к DOM
В Pub/Sub компоненты общаются через тематические каналы → нулевая связь между отправителем и получателем
Безусловно, событийность — не новинка. Но именно сейчас, когда фронтенд дорос до сложности микросервисных систем, нам, imho, нужны подходы уровня RabbitMQ, а не табличек «Продам гараж».
Как писал Дейкстра: «Инструменты влияют на образ наших мыслей». jQuery-события учили нас реагировать, Pub/Sub учит строить коммуникации.
Нафига на фронте менеджер очередей? Ваш фронт вот он - загружен в одном месте в рамках одного устройства. Событийности хватит "за глаза". Менеджеры очередей на бэке нужны для общения разнородных сервисов на физически или виртуально разных устройствах. Или когда с десяток продюсеров и десяток же консьюмеров. Или для сглаживания нагрузки, но всегда в рамках разных сервисов. Пожалейте своих коллег, не плодите сущности.
Менеджеры очередей на бэке нужны для общения разнородных сервисов на физически или виртуально разных устройствах
Нет, главная фишка это снижение связанности компонентов. Сервис авторизации фигачит сообщения USER_LOGGED_IN и USER_LOGGED_OUT и ничего не знает про десятки других сервисов, которые на это реагируют. А эти "другие сервисы" знают только про структуру сообщений и всё, т.е. заменить сервис авторизации например моком не стоит вообще ничего.
А в чем отличие от redux?
Отсутствием центрального состояния которое хранит это все. ИМХО тут чистый Event Emmiter получается. Надо оно нам, да вроде нет. Сейчас Flux решает все проблемы, т.к. совмещает в себе и функцию хранения и функцию подписки и отправки сообщений.
В моём сценарии есть всего две функции pub и sub.
Redux это такой комбайн на стероидах.
При этом, если у вас в скажем мобильном приложении уже есть redux, можно использовать и его, почему нет.
На фронте можно ещё проще, это же не многопользовательский режим. В рамках одной формы на каждый клик кнопки, на каждое изменение реквизита форма собирает со всех кортеж значений, обрабатывает скриптом-обработчиком (на любом языке) из нажатого компонента и рассылает всем результат в виде нового кортежа значений.
Вы правы, события во фронтенде действительно существуют со времён jQuery. Но есть нюанс: в те годы мы говорили о событиях DOM — кликах, ховерах, сабмитах.
Ключевое слово - мы, т.е. конкретно вы, лично. Не обобщайте свой опыт на всю индустрию.
Потому что на pub/sub в виде либо кастомных DOM-eventов, либо самописных eventbus-ов жили почти все крупные проекты со сколь-нибудь продуманной архитектурой. Ну и про backbone выше уже тоже сказали. И это мы еще Flash и, прости господи, Silverlight не вспоминаем.
Вы правы, события во фронтенде действительно существуют со времён jQuery. Но есть нюанс: в те годы мы говорили о событиях DOM — кликах, ховерах, сабмитах.
Про observer не забывайте.
Однако, соглашусь, что это всё не было шибко распространено и стандартизировано.
Вот насчёт типизации событий. Как бы вы посоветовали описывать схему событий так, чтобы её мог использовать любой проект-микросервис. Хотя бы на этапе разработки.
Второй вопрос: имеет ли смысл навешивать на классы событий метаданные, описывающие, в какие очереди эти события отправляются? Или это неудачное разделение обязанностей?
Не совсем понимаю проблематику первого вопроса... Вся событийная модель сводится к брокеру сообщений, самим сообщениям и зонам их рассылки, она универсальная по своей сути. Есть рутинные события, вроде "Пользователь залогинился", которые повторяются от приложения к приложению, но основная сетка событий строится уникально под каждое приложение, исходя из его функционала. Если вы уточните свою мысль, я попробую сформулировать что-то более внятное в ответ.
По второму вопросу:
Добавлять метаданные об очередях напрямую в классы событий — это архитектурный антипаттерн:
Нарушение инкапсуляции
Событие — это констатация факта («Пользователь зарегистрировался»), а не инструкция по его обработке. Когда событие знает, куда его отправить, оно берёт на себя чужую ответственность — как если бы письмо диктовало почте, в какой ящик его положить.Смешение слоёв
Информация об очередях — это инфраструктурная деталь. Зашивая её в доменный объект (событие), вы связываете бизнес-логику с технической реализацией.Риск vendor lock-in
Если события привязаны к конкретным очередям, переход на другую систему потребует правки всех событий. Это превращает миграцию в ад, где «просто поменять брокера» уже не получится.Усложнение тестирования
События с метаданными сложнее тестировать: теперь вам нужно имитировать не только данные, но и инфраструктурный контекст.
Альтернатива:
Вынесите маршрутизацию в отдельный Routing Layer — сервис или конфиг, который знает:
Какие события в какие очереди направлять;
Как реагировать на изменения инфраструктуры.
Это сохранит события «чистыми». Например, если завтра вы захотите дублировать события в две очереди вместо одной, достаточно обновить конфиг, а не переписывать тысячу классов.
Использовать слово "антипаттерн" - это антипаттерн. А уж настолько облениться, что прогонять ответ на комментарий через LLM - это супер-антипаттрн.
Вопросы сугубо прагматические. У меня есть несколько сервисов, реализованных на одном и том же языке. Для успешного обмена событиями у них должно быть достигнуто соглашение:
Каков смысл определённого события (это не внести в код, только документировать)
Каков состав определённого события (это легко решается описанием DTO-классов для содержимого событий)
В каком топике должно быть помещено определённое событие
Соответственно, по вопросу 2 возникает проблема: DTO должны быть объявлены в каждом сервисе, который их использует. Иными словами, значительная часть DTO будет требоваться более чем одному сервису. Дублировать это описание в каждый сервис = создавать себе головную боль в дальнейшем, когда потребуется поддерживать. Хочется иметь один источник истины в проекте. Встречал советы использовать git submodules, но это выглядит довольно громоздко. Хотя, зато случайно внести изменения в DTO будет сложнее.
По вопросу 3 схожая проблема: разные топики несут сообщения с разным смыслом. Если одно и то же событие идёт в два разных топика (не очереди) - это, скорее всего, два семантически разных события, хотя они и имеют идентичное содержимое в настоящий момент. В этом случае я нахожу куда более вероятным, что со временем одно из них изменится, и нам всё равно потребуется два разных класса. И разные сервисы должны соглашаться относительно того, какой тип событий куда идёт.
Ну и наконец, у меня зреет подозрение, что broker-agnostic подход - это такой же миф, как database-agnostic. В теории звучит круто, на практике миграция всё равно потребует полного рефакторинга системы - если миграция вообще потребуется. Стоит ли огород городить?
имеет ли смысл навешивать на классы событий метаданные, описывающие, в какие очереди эти события отправляются
Нет, не имеет. Событие это просто DTO. Их маршрутизация должна настраиваться совершенно на другом уровне. Например, на проде одна "маршрутная карта", у разработчиков другая (без очередей и асинхронщины), для тестов третья.
Где вы набираетесь этой манеры излагать: с драматизацией, с громкими словами, с пафосом? Это звучит как клоунство. На конференциях что ли? По тону сразу чувствуется, что тебе будут что-то продавать, нахваливая достоинства, скрывая проблемы и недостатки.
«Но ведь события усложняют отладку!» — скажете вы. Отвечу историей: когда в нашем проекте внедрили событийную модель, новый разработчик за день подключил фичу, которую раньше делали бы неделю. Он просто нашёл нужное событие в документации и подписался.
Уж ответил так ответил, просто размазал аргументацией.
В целом статья вроде норм, но было бы лучше, если бы часть "pub/sub решит все ваши проблемы" была поменьше.
Забавно, что в приведённой цитате нет ответа на аргумент про сложность отладки)
Т.е. мало того, что аргументация слабая, так она, как будто, и не по делу, вообще
Он просто нашёл нужное событие в документации и подписался.
А событие никогда не происходит т.к. его код его вызова удалило мержом. Пара пара пам-фьюх.
Самописная шина из 20 строк кода может быть лучше NgRx или postboy для проекта на 15 компонентов. Но когда система разрастается до 200+ акторов — ищите решения с типизацией.
Если отбросить в сторону проблемы event-driven, то всю статью можно сократить до нескольких строк:
Ребята, у нас есть «кафка*» в браузере. Вот как она работает:
Можно использовать CustomEvent как есть, но получится «многословно» и с подсказками в IDE будет не очень, поэтому формируем реестр событий с единым API и храним их в одном месте. Api, например, такой:
interface PriceChangedEventPayload {
productId: string;
newPrice: number;
}
export class PriceChangedEvent extends CustomEvent<PriceChangedEventPayload> {
init: CustomEventInit<PriceChangedEventPayload>;
// избавляемся от бойлерплейта при создании событий
constructor(detail: PriceChangedEventPayload) {
const init = { detail }
super('onPriceChanged', init);
this.init = init;
// избавляемся от бойлерплейта при отправке событий
dispatchEvent(this);
}
}
// Добавляем подсказки для IDE и Typescript-а
declare global {
interface WindowEventMap {
onPriceChange: PriceChangedEvent;
}
}
Ну и пользуемся:
// где-то в одном месте
addEventListener('onPriceChange', (event) => {
// ...
});
// где-то в другом месте
import { PriceChangedEvent } from 'registry'
if (priceWasChanged) {
// Создание экземпляра сразу диспатчит событие
new PriceChangedEvent({ productId, newPrice });
}
*Конечно это не кафка, но:
Формат сообщения = единственный контракт
Издатель не знает подписчиков
Брокер гарантирует доставку
15 КБ кода на Pub/Sub
10 строчек всего, вот рабочий пример. Если 10 файлов вам пришлось править, то может вам досталось плохо-спроектированное приложение?
export class Observable {
#callbacks = new Set();
notify() {
for (const fn of this.#callbacks) fn();
}
subscribe(callback) {
this.#callbacks.add(callback);
return () => this.#callbacks.delete(callback); // unsubscribe
}
}
Ну мы уже имеем ситуацию, когда большинство фронтендеров не умеет верстать.
Причин тому несколько, но одна из них - TS. Типизация и прочее "правильное" программирование настолько заняло умы и утилизировало столько когнитивных ресурсов, что учиться ровно поставить иконку уже банально некогда.
Это как если бы каждый сотрудник офиса передавал документы лично в руки, вместо того чтобы использовать общий ящик для писем.
Аналогия с почтой плохая: там отправителю заранее известен получатель сообщения, и конкретно документы сотрудник передаёт в соответствии со своим процессом, то есть сам находит ответственного, даже если отправляет через почтальона-посредника. Через общую рассылку уходят только общие уведомления.
this.authService.login().subscribe(() => {
eventBus.publish(new UserLoggedInEvent());
});
Плохо. Гораздо лучше так:
class AuthService {
@Observable
user: User;
login() {
...
this.user = user;
}
}
Когда поле user
обновится, все заинтересованные сами узнают об этом через механизм реактивности. Даже Angular начал внедрять сигналы.
Паттерн бэкенда:
Если сервис-потребитель упал, RabbitMQ сохранит сообщения в очереди, пока он не оживёт.
Фронтенд-адаптация:
Для критичных событий (например, аналитики) реализуем повторную отправку:
Выглядит неэквивалентно. У вас получается, что сервис аналитики должен сам кешировать сообщения из шины. И это даже не очередь, если сервис аналитики лежал или упал, то заново он это сообщение не получит. Если вы хотите воплощать Кафку на фронтенде, то собирайте односвязный список сообщений, пусть у каждого подписчика свой курсор будет, вводите retention policy. Тогда перезапущенный или вновь поднятый сервис аналитики перечитает недополученные сообщения и всё переделает.
они же дают свободу, сравнимую с переходом от монархии к демократии
Не, это, скорее, охлократия — власть толпы
начать описывать их взаимодействие как договор равных
Это нарушает принцип единственной ответственности и снижает связность (cohesion): любой модуль может отослать в шину любое сообщение, даже если отправитель не владеет этой информацией.
Пример: как и выше есть сервис Auth, который логинит пользователя и рассылает сообщение UserLoggedInEvent. Есть некий сервис Foo, который выполняет какие-то действия при получении UserLoggedInEvent. Какой-то разработчик обнаружил, что ему нужно снова запускать какие-то действия в сервисе Foo уже после логина. Обычно такая история случается во время фикса бага. Он находит самое простое и тупое решение: из сервиса Bar начинает самостоятельно рассылать сообщение UserLoggedInEvent, чтобы инициировать действия в сервисе Foo. На самом же деле проблема в том, что сервис Foo неявно зависит и от каких-то других данных, но эту зависимость не оформили явно.
Реактивная же модель, в отличие от модели pub/sub, самостоятельно обновляет данные, если обновился их источник. Фактически в ней тоже происходит рассылка сообщений, но более точечная, и только тем, кто в этом заинтересован, а сервисы-источники данных остаются единственными владельцами этих данных (source of truth).
Вообще то весь js особенно в бразуере построен на событиях и пабсаб в разных видах широко используется, какая то абсолютно ложная посылка в начале статьи.
ну и про ангуляр - может поэтому многие не переключались на него - он неудобный и заставляет писать много сервисного кода, держа в памяти 10-20 активных встроенных сущностей. Это перебор.
фронтенд-компоненты общались через цепочки Input/Output, словно это 2005-й, а мы пишем WinForms.
А что такое "цепочки Input/Output" в WinForms? Я долгое время работаю с WinForms, но этого не знаю.
на фронтенде компоненты перешёптываются через пропсы, будто подростки на школьной дискотеке.
А можно объяснить, что такое "проопсы" (для старпёров типа меня, которые не знают современного молодежного жаргона)?
Видимо, про JSX и подобные вещи речь, когда атрибуты являются значениями свойств объекта, передаваемого в компонент. Свойства = пропсы.
<Component some={1} />
some это проп (от property, свойство) со значением 1
Это типа свойства компонентов (properties) или props сокращенно. Всякие данные для передачи вниз по дереву. От цвета кнопчек вплоть до всяких коллбеков и самих компонентов.Не то чтобы это современный жаргон а скорее наоборот дремучий.
А разве angular не умер ещё ? Я помню ещё лет 10 назад делали на нем проект и после него даже vue уже казался пушка фоеймворк. Потом несколько лет юзали react и вот из всех веб фреймворков он зашёл больше всего. И уже тогда лет 7-8 назад он считался самым тяжёлым и тормозным а потом как мне казалось и вовсе канул в лету. С тех пор я мигрировал в мобилки пару лет с react native и вот уже года 4 как работаю с флаттером. А тут вот оказывается кто то ещё пишет на angular. Сорри за оффтоп просто удивило это. Ну а что касается event driven то мне кажется это больше про бекенд даже скорее полезно когда нужно объединить какие то независимые системы. К примеру у вас есть код или какая то часть общей системы и нужно его подружить с другой системой, возможно даже написанной на другом языке. Через события это удобно и у меня был опыт внедрения scala kafka в один такой проект. Полагаю есть ещё много областей для применения этих технологий но строить архитектуру на event bus и подобных техниках я бы крайне не рекомендовал.
Покажите мне хоть один огромный проект который живет с хорошей событийной типизированной архитектурой – я про такие на клиенте не слышал, к сожалению.
Через пропсы достигается одно мощное преимущество – всегда можно быть уверенным что контракт взаимодействия соблюдается, и не важно, это код 10-ти летней давности, или новый. На больших проектах иногда работают сотни разработчиков из десятков команд, и проблема сборки больших продуктов так, чтобы убедиться что изменение кодовой базы или зависимостей не приводит к нарушения старых контрактов взаимодействия, это серьезный вопрос, и строгая типизация (в том числе через пропсы) снимает этот риск в значительной степени, но создает другие проблемы, да.
Проблема не в связности, а в излишней связности – нет архитектуры под изменения, или пытаются расширить код там где нужно писать новое решение, и так далее.
Ну и напоследок – есть такой прием.. можно отслеживать уровень ответственности модулей, и делать дублирование если у них разные причины на изменение – так можно избежать сверхответственных модулей, и значительно упростить поддержку системы. Если завязать тысячу модулей на один общий модуль, в который дописывать то одно условие, то другое, чтобы угодить всем зависимым модулям, то ничего хорошего не получится, и не важно, это подписка через события, или прямая связь с импортом и вызовом. Поэтому дублирование – не всегда плохо, оно позволяет решать такие проблемы там где возникают разные причины на изменение.
Поэтому дело совсем не в пропсах или событиях, а скорее в уровне кадров, который сейчас довольно низкий, в основном ребята которые могут написать всё, но не смогут дописать и развить это всё через пару лет потому что шаг влево-вправо и оно развалится )
Минусы Pub/Sub:
Если по событию выполняется команда, вы не знаете она была выполнена успешно или нет.
Если подписчика больще одного, то непонятно в каком порядке они выполняются. Если говорят, что в порядке добавления, то это ненадежно.
Promise лишены этих недостатков.
P.S. Не люблю, когда приходят бекендеры, которые думают что они умнее всех и начинают рассказывать фронтам, как им надо жить.
а это точно недостатки? само название PubSub показывает, что это не про команды, а про оповещения. А вопрос "в каком порядке они выполняются" вообще не имеет смысла, у нас в самых нищебродских процессорах сегодня больше 10 ядер, они все должны выполняться, а не "в порядке".
у нас в самых нищебродских процессорах сегодня больше 10 ядер, они все должны выполняться, а не "в порядке".
ЕМНИП в браузере Event Loop идет в одном потоке. И даже если использовать Worker'ы, до DOM им недоступен ("you can't directly manipulate the DOM from inside a worker", [из MDN](you can't directly manipulate the DOM from inside a worker,) ). В десктопном мире всё устроено примерно так же: WinForms и WPF в C# имеют однопоточный SynchronizationContext, и ко всем элементом GUI надо обращаться только из него (теоретически, в Windows оконные процедуры разных окон могут работать в разных потоках, но этой возможностью редко кто пользуется).
в браузере Event Loop идет в одном потоке
Учитывая, что тут каффку греффневую вспомнили, то явно вопрос о концепциях в целом, а не только о какой-то единственной реализации, типа V8.
в Windows оконные процедуры разных окон могут работать в разных потоках
WinForms же на основе Delphi VCL создавали. Так что скорее всего дело в синхронизации глобального кэша C#-объектов: шрифтов, кистей и т.д. Чтобы хэндлы не плодить до бесконечности при каждом изменении типа Font1.Size += 2. И чтобы не париться в отдельном кэше на каждый отдельный поток. Не то, чтобы это было сложно сделать, а просто не нужно. Новичков запутает, а понимающие и сами сделают, если в конкретной задаче надо.
Вообще, на Delphi это сделать тривиально, просто каждый поток запускать в отдельной DLL (в которых в каждой будет независимая RTL, следовательно нет и синхронизации). Не знаю, есть ли "чистые" DLL с шарпе, предполагаю что нет, и там они все составляют единое дерево классов от самого корня. Просто не нужно практически.
Но почему сразу ограничиваться десктопом (DOM или WPF - не важно)? Если речь про разные микросервисы с кроликами, если их взяли за идеальный образец для репликации, то тогда у отдельных "клиентов" шины просто нет разделяемого состояния, нет синхронизации, нет проблем одновременного исполнения.
Даже в статье список: "Модуль C слушает то же событие для расчёта доставки, Модуль D пишет в LocalStorage" - это не про "рисовать окошки" (хотя C вероятно после расчёта и запросит такое изменение, но запросить можно тоже асинхронно). Тут у нас типовые расчетно-загрузочные worker thread, который GUI не трогают. Проблемы тяжеловесности kernel threads тоже можно избежать, поскольку код JS, а не нативный. Хотя спор о целесообразности green threads в JVM/CLR так вроде однозначно не решился.
Но в любом случае, если мы выходим за рамки конкретного инструмента, и начинаем думать о концепции в целом и её примерах в других стеках, то мне кажется правильнее разделить мысли на, сначала, идею в общем виде, а потом уже только "накладывать ограничения" конкретной виртуальной машины или компилятора. А уж тем более завязывать логику на недокументированные и необязательные детали реализации ("непонятно в каком порядке" будет линейный обход массива, ужас что такое)
а не только о какой-то единственной реализации, типа V8.
А вы уверены, что именно о единственнной реализации, а не всей экосистемы в целом, начиная с ECMAScript?
WinForms же на основе Delphi VCL создавали.
Вообще-то - делали с нуля: код VCL принадлежал не MS, а другому владелцьцу. Оптяь же VCL тоже была однопоточной, и не без причины: первую версию Delphi и VCL делали под Win3.x, где один поток был не то что на приложение, а на все графические приложения сразу, и если он стопорился, то всё что мог делать пользователь - это смотреть на песочные часы. Паэтому многопоточного доступа изначально никто не закладывал - а дльше уже начала играть роль совместимость.
PS А на остальное я отвечать не буду: я от фронта далёк (и это к счастью). И хотя сама идея тащить микросервисный бардак ещё и на веб-страницу мне не нравится, но возразить аргументированно по ее поводу у меня не хватает знаний.
Вы когда-нибудь замечали, как цифровой мир движется по спирали?
А куда ему ещё двигаться, если там сплошной цикл "лижем там, где больно" и при этом "надо написать что-то новое и назвать новым словом"
Это обычный осциллятор, как пробки на дорогах:
Пробка на улице Иванова
Все посмотрели в навигатор, и потому что все умные - все поехали на улицу Петрова
Пробка на улице Петрова, улица Иванова пустая
Все посмотрели в навигатор...
Очевидно (должно быть), что у любого инструмента есть плюса и минусы. Свалка отходов, в каком-то смысле. Чем чаще используется Самый Модный Сегодня Инструмент, тем больше в мире накапливается его отходов.
Наконец, накапливается так много, что "низы не хотят" и происходит революция, изобретают инструмент, который не оставляет этого конкретного отхода. Проще говоря, слабая сторона Инструмента1 будет сильной стороной Инструмента2. Но скорее всего - ведь задачи те же самый, теперь у Инструмента2 его слабая сторона та же, какая была сильной стороной Инструмента1.
Но про это пока никто не думает, у всех эйфория, "лижем где больно". Кроме того, про это говорить вообще вредно для продаж Инструмента2.
Проходит какое-то время, старую свалку в самом деле разгребли, но накапливается другая свалка, и происходит Событие: чтобы разгрести отходы Инструмента2 придумывают революционный Инструмент3... почему-то очень похожий на старый и проклятый Инструмент1. Хотя цикл может быть и длиннее, чем два инструмента.
...когда-то конфигурации 1С скорее всего были в самом деле конфигурациями. Но complexity has to live somehwere и пришлось разрешить в конфигурации добавить скрипты, как исключение конечно. А потом ещё. И наконец "конфигурацией" стали называть программу на спец-языке. И у них, как у большинства программ, появились конфигурации конфигураций, пока ещё простые наверное.
...когда-то в CSS не было функций. Потом появились маленькие и не гибкие. В конце концов веб-программисты дорастут до откровения, что для бесплатной гибкости в CSS нужен полноценный тьюринг-полный язык с монадами и потоками. Когда это случиться, придумают много языков конфигураций для CSS-программ, сторонники этих языков начнут интернет-войны, после которых самым популярным какой-то один, и он почему-то будет сильно напоминать CSS 1.0
Интересный подход. Мне интересно еще вот что: судя под всему, проект, на основе которого вы сделали такие выводы
Всего 12 файлов для одной кнопки! В мире микросервисов это выглядело бы как правка пяти разных репозиториев, чтобы поменять цвет лейбла. И тогда я задался вопросом: «Почему фронтенд, при всей своей прогрессивности, до сих пор не усвоил уроки распределённых систем?»
Это не что иное, как просто криво написанный код, без учета того, что в будущем он может меняться, адаптироваться и быть готовым к модификации (это уже вопрос к принципам ООП, которые не были соблюдены). И тут тогда проблема не в том, что фронт не учел уроки, а что проект был изначально написан криво.
В Angular-компонентах застряли пережитки эры империй:
Жёсткая иерархия вызовов
Сервисы как ESB-монстры
События через 5 уровней — как бюрократическая почта
Понимаю, что речь про Angular, но в том же React можно было бы обойтись как Redux (как поминали выше), так и Context.
Мои слова не отменяют смысл статьи и ее подход, просто как будто аргументация проблемы выстроена слабовато.
Что только люди ни придумают, лишь бы Redux не использовать…
ИМХО, Фронтенд практически всегда был и будет адской мешаниной костылей, странных решений и отсутствия здравого смысла при проектировании.
У этого есть разные причины, но основная - человеческая лень и разгильдяйство 😁
Что уже много лет характерно для фронтенда:
Подход имени Вовки из Тридевятого Царства в "управлении проектом" (Фигак, Фигак. И в продакшн)
"Зачем проектировать структуры и связи? Вы практически тоже самое уже где-нибудь делали"
"На верстку посадим самого дешёвого в команде. Или пусть разработчик её бесплатно делает"
"А ещё нам надо, чтобы веб-приложение работало в последней версии Chrome и в IE 10 ( а ещё лучше в IE 6) "
"Ну на IPad згд вроде выглядит нормально... А под АРМ технологов и Андроид-плангеты мастеров подшаманите приложение как-нибудь в следующем релизе"
"Веб-Приложению нужен креативный интерактив... И чтобы было место куда рекламный банер или ленту сторис встроит"
Тут все просто - подход с использованием глобального состояния по очевидным причинам устарел, буквально, десятки лет назад. Его пытаются время от времени переизобретать (как в редаксе каком-нибудь), но этот антипаттерн по понятным причинам не взлетает и взлететь не может. Последние десятилетия развития промышленного программирования как раз и сводились к изобретению техник, которые позволили бы избежать превращению приложения в глобальную мусорку, где кто угодно может насрать в общий контекст чем угодно, в результате чего логика взаимодействия между компонентами становится принципиально неотслеживаемой, а внесение изменений - крайне тяжелым и муторным из-за резкого повышения зацепления (отредактировать 12 файлов для изменения кнопки? подержите мое пиво - с обмазанным сагами редаксом пришлось бы редактировать все 30), так что вполне логично что сообщество достаточно жестко сопротивляется любым попыткам вернуть все назад во времена первого ангуляра.
Чувак попал на проект с говнокодом и обобщил своё впечатление на всю индустрию
Кстати по поводу ngrx - я голосую за ngxs
Про Kafka: Если вам приходит 1000000 запросов в секунду на создание объектов, то вы на backend-е задумываетесь о масштабировании и использовании Kafka или еще чего. На Фронте такого не бывает. при чем тут Кафка?
Про any: Какое отношение any имеет к событиям и Input/output я не знаю. Настройте себе linter чтобы ругался на все any и живите счастливо.
input/output vs events: input/output обеспечивают максимальную изолированность компонента - он тупой, он не знает ни про что, кроме Input и output, получает-отдаёт. В бибилотеках большинство компонент именно тупые - с ними просто работать, их просто понимать. Зачем компоненту кнопки, выпадающего списка или степпера нужно что-то еще для меня загадка. И да это может привести к длинной цепочке вызовов, и да иногда это заменяют на какой-то event-bus, и да это делают уже 10+ лет.
И да когда появляется необходимость в сложном состоянии приложения, может быть частичный переход от input-ов к локальным/глобальным сервисам хранящими observable, но вроде статья это вообще игнорирует, поэтому не углубляемся.
Про утечки памяти: ngOnInit у сервисов? А про необходимость отписки знают все, кто пишет на angular.
Про debounce: Если в нормальной команде для устранения циклический зависимостей кто-то попытается использовать debounce, он никогда не пройдёт code review. Т.к. это банально означает, что ты вообще не понимаешь что происходит.
Про modalService: ну это же просто &^%да - посмотри как реализованы модалки в Angular Material:
dialog.open
(UserProfileComponent,...)
ну какой сервис тут будет на 1200 строк? Ну кто прописывает модалку в template компонента, который её открывает? Ну кому нужно вместо прямого вызова постить какой-то event?
Мы пробовали это и это с треском провалилось.
У нас кастомизированный FSD, NextJS, effector. Не смотря на хорошую архитектуру, документацию, типизированные контракты, ts и линтинг, код становится слишком запутанным.
Пример: у нас было действие которое дергало несколько других фичей ивентом, затем после того как работа каждой закончена, так же с помощью ивентов, финальный результат отправлялся на бэк, ожидался ответ, а затем новыми ивентами дергались другие вещи. Это породило хаос. Что и откуда прилетает не понятно. Повторюсь, все было хорошо документировано в коде, но что бы понять что делает та или иная фича, нужно было порой потратить минут 20, тогда как при классических подходах это занимало 2 минуты. Мы отказались от этого подхода и решили, что это не для фронта. FSD достаточно для удобного поддержания клиента.
P.S. если вам надо редактировать 12 файлов для добавления кнопки скорее всего вы работаете с быдлокодом
Диалектику которую Гегель ввёл и Маркс с материализмом совместил ещё не отменили. Почему тут не должно быть движения по спирали.
У меня есть и другой пример- централизация распределённость. От маенфреймов с терминалами удалёнными. мы перешли к автономным ПК. И даже КПК были вполне вещью в себе. А потом -обратно, вместо клиентского ПО в котором реализован весь необходимый функционал к ПК с браузером как тонкому клиенту всевозможных веб систем которые вертятся на сервере.
Основная проблема с event-driven архитектурой - это снижение прозрачности в runtime. Когда бизнес-логика ОДНОГО связонного бизнес процесса разносится по множеству подписчиков, становится крайне сложно отследить, кто инициирует действие, в каком порядке обрабатываются события, какие компоненты участвуют в цепочке на каком этапе БИЗНЕС процес. Состояние начинает быть размазанным по разным компонентам.
Пример с циркулярными событиями -- симптом этой сложности.
Прблемы с отладкой -- В отличие от прямого вызова, при асинхронной передаче событий стек вызовов теряется не понятно откуда событие прилетает и чем оно было инициировано, как результатполучаются сложные сети событий, что ведет к тому что во первых IDE не может помочь в работе (не может проследить цепочку) и к тому что резко растут требования к документации нужны схемы потоков событий, архитектурные диаграммы и пр. в противном случае разработчики быстро теряют понимание, как работает система в целом.
Проблема с версиями событий... вы так изящно перешли с V1 на V2 но в реальном то мире это не работает, нужно будет что то что будет транслировать V2 в V1 иначе у вас получается 2 параленльных системы. И там начинается :) и я еще не начал про идемпотентность
Статья предлагает применить отвертку вместо ключа, ведь так модно и так делают там, другие.
Антихрупкость: Системы учатся жить с ошибками (вспомним принцип Dead Letter Queues)
Вообще Неуязвимость; сомневаюсь, что ваша шушлайка станет лучше от наплыва кривых сообщений
Фронтенд — новый легаси: Как мы проспали event-driven революцию