Comments 117
Фронтенд всегда был 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, можно использовать и его, почему нет.
На фронте можно ещё проще, это же не многопользовательский режим. В рамках одной формы на каждый клик кнопки, на каждое изменение реквизита форма собирает со всех кортеж значений, обрабатывает скриптом-обработчиком (на любом языке) из нажатого компонента и рассылает всем результат в виде нового кортежа значений.
Для этого же можно использовать легковесный стейтменеджер, например zustand, с персональным стейтом под компонент и прокидыванием его, или только части его в индексный файл компонента. Мне кажется, это не сильно будет отличаться от того, что вы описали и вполне себе обычная практика на проектах
Вы правы, события во фронтенде действительно существуют со времён 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. В теории звучит круто, на практике миграция всё равно потребует полного рефакторинга системы - если миграция вообще потребуется. Стоит ли огород городить?
Дублировать это описание в каждый сервис = создавать себе головную боль в дальнейшем, когда потребуется поддерживать. Хочется иметь один источник истины в проекте. Встречал советы использовать git submodules, но это выглядит довольно громоздко.
А что мешает их сделать зависимостью?
Хммм. Ну если речь про шарп, то да, это идея - библиотека классов, и все дела.
А вот как быть с языками вроде питона... Поднимать свой репозиторий и делать приватный pip-пакет?
Вариантов тут масса, и все не удобные:
В случае с monorepository, мы можем выделить локальную библиотеку; в вакууме это наиболее чистое и быстрое решение, которое позволяет не размазывать код по десятку репозиториев. Из минусов: необходимость отдельных build'ов этой библиотеки, что на фоне прочих вариантов - мелочь. Но, как я понимаю, это не ваш сценарий.
npm'ка. Никаких проблем поднять на рабочем серваке какой-нибудь Verdaccio сегодня, imho, нет. Получаем типизированный набор событий, с возможностью хранения некой меты, а не просто DTO. Минусы:
на коленке тут ничего не поправишь - ради каждого чиха нужно вносить изменения в репозиторий библиотеки и поднимать версию
портирование изменений во множестве модулей превращается в жуткую рутину
минимальное изменение ведет к фактической инвалидации всего приложения, ибо кто его знает чего там могло сломаться... Зато у тестеров всегда есть работа.
submodules. То же самое, что и второй пункт, но, imho, в два раза болезненнее из-за танцев с git'ом. Я бы вообще не смотрел в эту сторону, но это моя личная боль и детская травма, возможно.
Из странноватых решений есть еще некое переосмысление Module Federation (оригинал даже не предлагаю, ибо это вообще дичь, на мой вкус; но, если не в курсе - почитайте и про него). Если нас устроят исключительно DTO, чистые от каких-либо данных, то можно описать их в бэке и прокинуть через swagger, подтягивая в проект каким-нибудь автоматическим генератором моделей на основе api (openapi-gen). Тема интересная, но тут стоит пятьдесят раз подумать, потому что ограничения у этой штуки понятные - никаких id событий и прочего туда уже не пихнешь - канонические DTO. Зато получаем чистый источник единственной истины во всех проектах.
Но, в целом, мне кажется, что сама постановка у вас какая-то странная. События принадлежат конкретному приложению, зачем приложению А обладать такими же событиями, что и у В? Они способны ими обмениваться через какой-нибудь SignalR? Тогда это уже точно вовлечение бэка и решать это надо там.
Если речь о простой унификации в духе "везде должно быть одинаково"... Это ведет нас к армейскому "круглое тащить, квадратное катить", столько боли с поддержкой cross-моделей не стоят этой красоты однообразия, imho.
---
По части broker-agnostic не понимаю ваших сомнений. Если речь идет о чистом брокере сообщений (и о корректном его использовании). То клиенты пользуются им в очень ограниченных сценариях в духе .subscribe()
/publish()
. Создайте вокруг брокера самописный слой абстракции и пользуйтесь только им, при смене брокера все изменения будут лежать внутри нашего кастомного решения. Этот подход не сработает, если вы завязываетесь на какие-то сложные механизмы взаимодействия с оригинальным брокером, но тут уже проблема не в порочности brocker-agnostic, а в неправильном использовании самого паттерна Message
.
Спасибо за развёрнутый ответ. Но вот этот момент меня озадачил:
События принадлежат конкретному приложению
Как событие может принадлежать приложению, если события - инструмент коммуникации между приложениями? Ну да, технически приложение может отправить событие себе... но, имхо, это далеко не основной сценарий.
Я рассуждал в контексте статьи, а она о взаимодействии элементов внутри приложения.
Ок, но тогда возникает множество вопросов в духе
- А как выглядит архитектура взаимодействия компонентов системы?
- Как устроена шина передачи сообщений? Какова её ответственность?
И далее по тексту... В вакууме, я бы предложил идти либо по пути npm, либо развить описанный мной п.4 по следующему пути: определяем стабильные модели на стороне бэка, но не как DTO, а как значимые объекты с содержимым, пишем какой-нибудь веселый скрипт в духе того же openapi-gen для автогенерации моделей; так получаем единый источник типизированных моделей, и даже обладающих фактическим знанием (id сообщений, например); отсутствие версионности здесь одновременно и плюс и минус, но танцев с бубном в процессе использования явно сильно меньше, чем с самостоятельными библиотеками. Путь довольно скользкий, но, как полёт фантазии и экспромт "на тему", выглядит интересно.
P.S. Можете и в личку писать, - больше шанс, что отвечу более оперативно.
имеет ли смысл навешивать на классы событий метаданные, описывающие, в какие очереди эти события отправляются
Нет, не имеет. Событие это просто 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. Типизация и прочее "правильное" программирование настолько заняло умы и утилизировало столько когнитивных ресурсов, что учиться ровно поставить иконку уже банально некогда.
к счастью это больше не нужно уметь - ллм прекрасно верстают и знают все трюки css.
А какое решение? Отказаться от 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 и подобных техниках я бы крайне не рекомендовал.
Писал на Vue, сейчас на Angular. Разница видится непринципиальной - скорее дело вкуса.
По поводу шины полностью с вами согласен, далеко не везде оно нужно.
Не совсем понятно, какая версия имеется ввиду. AngularJs(первая версия, без typescript) - практически не используется, Angular 2+(и выше, текущая - 19 версия) - вполне себе используется.
Покажите мне хоть один огромный проект который живет с хорошей событийной типизированной архитектурой – я про такие на клиенте не слышал, к сожалению.
Через пропсы достигается одно мощное преимущество – всегда можно быть уверенным что контракт взаимодействия соблюдается, и не важно, это код 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), так что вполне логично что сообщество достаточно жестко сопротивляется любым попыткам вернуть все назад во времена первого ангуляра.
подход с использованием глобального состояния... пытаются время от времени переизобретать (как в редаксе каком-нибудь), но этот антипаттерн по понятным причинам не взлетает и взлететь не может
Redux – 61.2k звезд на Github, 4.8 млн пользователей, 995 контрибьюторов и через 10 лет существования всё еще 12 млн еженедельных скачиваний на npm – это Вы про это говорите "по понятным причинам не взлетает и взлететь не может" 🤦♂️
Большинство современных сайтов в интернете, которыми Вы пользуетесь, работают через Redux. Включая сайт, на котором Вы сейчас находитесь. Ваш комментарий про то, как Redux не работает, скорее всего, хранится в Redux-сторе, видите, какая ирония.
Миллионы мух не могут же ошибаться.
Если вы считаете, что этот сайт работает удовлетворительно, то у вас крайне низкие запросы к жизни в целом и вебу в частности. Этот сайт работает как говно.
А какой сайт работает хорошо? Можно пример? И, желательно, чтобы он не использовал Redux или любое другое глобальное хранилище данных.
Если вас интересует, как нужно писать форумы — https://github.com/discourse/discourse, там руби и эмбер. Любой сайт на нём откройте, и посмотрите.
Большинство современных сайтов в интернете
продолжают работать на пыхе с небольшой добавкой jquery. SPA в целом - это история, которая "откусила" энтерпрайзно-десктопный сегмент, в "открытых интернетах" она крайне слабо представлена по целому ряду причин. 99% кода, который пишется на реакто-ангуляро-вью - это внутрикорпоративные приложения, которых вы ни в каком интернете никогда не увидите.
Redux – 61.2k звезд на Github, 4.8 млн пользователей, 995 контрибьюторов и через 10 лет существования всё еще 12 млн еженедельных скачиваний на npm – это Вы про это говорите "по понятным причинам не взлетает и взлететь не может"
Во-первых, количество школьников, которые скачивают пакет, чтобы написать очередной тудулист или гостевуху, не говорит ровным счетом ни о чем.
Во-вторых, надо понимать принципиальную разницу между способами использования редакса и подобных пакетов. Одно дело, когда мы по максимуму стараемся избежать глобального состояния, но там, где это необходимо, используем (и используем менеджер состояний, соответственно). И другое дело - редакс головного мозга, когда глобальное пространство становится мусоркой. Я встречал проект где люди вынуждены были прилагать к коду флоучарты (поддерживая их и старательно проверяя на рвеью каждый раз, ессно), т.к. из-за массивного использования редакса понять логику работы приложения из кода было уже просто невозможно.
В-третьих, уже и сам Дэнчик неоднократно говорил, что редакс - ненужное говно. Ну оно и понятно - он его писал, когда сам не имел ни знаний, ни опыта, ни какого-то базового понимания best practices. Практически любой начинающий программист генерит подобные штуки, только обычно понимание того, что нагенерил говно, приходит раньше, чем это говно может пойти в какие-то массы. А тут вот случился своего рода исторический казус, и говно завирусилось - бывает.
В-четвертых, если в целом говорить, глобальное состояние - это самое страшное, что вообще бывает в программировании. И практически все программирование сводится к тому, чтобы как можно меньше этого глобального состояния было. Все остальное - просто средства обеспечения этой базовой цели. Чтобы понять, почему это самый страшный антипаттерн - ну просто попробуйте пописать код, передавая данные между функциями не через аргументы, а через глобальный стейт. Т.е. - функции ни чего не принимают и не возвращают, а читают и пишут в переменные из глобального скоупа. И без локальных переменных, конечно же! Локальные переменные и это вот все придумали диды-ретрограды из прошлого века. Современному сениору с двухлетним опытом этот устаревший мусор НИНУЖОН.
Включая сайт, на котором Вы сейчас находитесь. Ваш комментарий про то, как Redux не работает, скорее всего, хранится в Redux-сторе, видите, какая ирония.
А, ну так это неудивительно тогда, что на страницах с 1к+ комментами хабр превращается в тормозное говно (это шутка если что, я в курсе, что это не из-за редакса, а из-за того, что разработчики хабра не понимают, как в браузерах устроен процесс рендеринга страницы).
Но тут кстати очень важный вопрос - а ЗАЧЕМ хранить комментарии в сторе? Это же глупо. В сторе требуется хранить данные, которые надо шарить между компонентами.
Никак не привыкну: если комментарий написан хорошим русским языком, внятно, вдумчиво и по делу — у автора почти наверняка карма в сильном минусе. Помог, чем мог.
редакс - ненужное говно
хорошим русским языком, внятно, вдумчиво и по делу
Никак не привыкну: если комментарий написан хорошим русским языком, внятно, вдумчиво и по делу — у автора почти наверняка карма в сильном минусе.
Ну да, демонстрацию самомнения (не важно, обоснованного или нет) народ не любит. А если демонстрирующий ещё и полезные статьи не пишет - ему совсем плохо становится. Здесь - именно тот случай.
Чувак попал на проект с говнокодом и обобщил своё впечатление на всю индустрию
Кстати по поводу 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)
Вообще Неуязвимость; сомневаюсь, что ваша шушлайка станет лучше от наплыва кривых сообщений
Во-первых, вы путаете Pub/Sub и акторную модель. Во-вторых, акторную модель предложил Карл Хьюитт, а в Xerox её просто впервые утилизировали в эзотерическом языке (хотя методы Алана Кая были фактически акторами, и фамилию человека, вообще всю эту кашу заварившего, в тексте маожно было бы и упомянуть). В-третьих, в 1986 был создан эрланг, и фамилию Джо Армстронга, впервые реализовавшего акторную модель в коммерчески-успешном индустриальном языке — тоже можно было бы упомянуть. В-четвертых, зная ключевые слова, легко отыскать в интернете кучу действительно хороших текстов примерно про это (и даже на хабре можно почитать про то, почему акторы — это не только и не столько Pub/Sub). И, в пятых, акторная модель уже реализована в браузерах, называется — WebWorkers.
Если бы в мире существовал хоть один эрудированный человек, занимающийся прикладным джаваскриптом (кроме Эрика Эллиота, который ушел в популяризаторство и Джона Ресига, который, кажется, наелся поддержкой своих библиотек) — правильную реализацию акторов в JS/TS затащили бы лет 10 назад,
Во-первых, вы путаете Pub/Sub и акторную модель.
А разве важно, как это назвать и уж, тем более - приоритет, кто это первым придумал? По-моему, для практики это совсем не важно.
правильную реализацию акторов в JS/TS затащили бы лет 10 назад,
Но - не затащили. И приходится теперь несчастным передовикам (фронтэндерам) ;-) обходиться тем, что есть, что позволили им производители браузеров, упираясь в существующие ограничения.
Для практики вообще ничего не важно, если начинать с изобретения колеса.
Но у умных людей, диссертации которых можно найти по их именам, попадаются хорошие решения.
Которые можно переписать с нуля, конечно. Но даже тогда лучше сперва с ними ознакомиться.
Но у умных людей, диссертации которых можно найти по их именам, попадаются хорошие решения.
"Узок круг этих умников, страшно далеки они от народа." (почти (с))
А народу - той его части, которой за разработку приложений бабло капает - нужны не диссертации, а фреймворки.
PS Впочем, есть прослойка людей, которые перерабатывают всю ту макулатуру, которые плодят умники, и делают из нее что-то полезное для простых людей. В моей молодости такие люди назывались прикладниками.
за разработку приложений бабло капает
Неожиданно для самого себя я однажды обнаружил, что если ознакомиться со всей этой скучной писаниной — бабло внезапно заметно увеличивается без всяких дополнительных усилий.
Ошибка выжившего, наверное ;-)
Вы про акторную модель Карла Хьюитта имеете ввиду или вообще?
Можете посоветовать удобоваримые материалы для тех кто "математику прогуливал"?
Максимум что читал это документация к Akka, как работают супервизоры в OTP.
Ну Хьюитт был первым, кто произнес вслух словосочетание «акторная модель», а потом еще целый диссер защитил :)
Но я имею в виду «вообще», конечно. OTP — это как раз правильная реализация акторной модели; читать про OTP — в принципе полезно для развития кругозора (про супервизоры — тоже, но они как раз к акторной модели имеют очень косвенное отношение). Мне в свое время очень помогла взглянуть на мир совершенно под другим углом диссертация Армстронга (там нет прям математики), но я в принципе не большой сторонник факультативного чтения.
Возьмите эликсир, почитайте по диагонали их Getting Started, запустите REPL и поиграйтесь с процессами (акторами) и сообщениями. Написать, что ли, текст про «как вкатиться в акторную модель»?
Кстати, если внимательно прочитать мой комментарий, можно узнать, что таки затащили в виде вебворкеров. Но это слишком сложно.
Вы проспасли переход на реакт, а не event-driven революцию
Если вам не всё равно, какой фреймворк использовать, значит вы просто не умеете программировать.
Благодарю за повод задать мой любимый вопрос вам: "Что это значит - уметь программировать". Пожалуйста, ответьте: "ваше мнение очень важно для нас"(с).
Я использую эту фразу как одобряемый администрацией ресурса способ сообщить другому комментатору, что он рылом не вышел для Калашного ряда.
Но я отвечу, запросто: уметь программировать (на мой облыжный взгляд) — означает способность разобраться в чужом средней сложности коде на незнакомом языке за ограниченное время (измеряемое часами).
Дело не в личных предпочтениях, а в скорости\стоимости разработки и поддержки кода. Или вы пишите фронт на чистом html\js раз вам всё равно?
Голословно обвинять неизвестного вам человека в том, что он не умеет программировать – это хамство. Я могу на куче разных фреймворках один и тот же фронт написать – это не значит, что мне "всё равно" на чем писать. Разные фреймворки – это разные паттерны, разные библиотеки, разные окружения. Есть best practices, а есть откровенные антипаттерны.
Если вы можете написать бэкенд на 10 разных языках, это не значит, что вы во фронтенде разбираетесь.
Голословно обвинять неизвестного вам человека в том, что он не умеет программировать – это хамство.
Как будто что-то плохое.
Ну если бы он сказал "Вы проспасли переход на $mol" как пример прогрессивного подхода на фронте, то еще можно было бы понять.
Но приводить в пример реакт, наводит на некоторые мысли о компетенции.
А какой лично вы порекомендуете?
Кажется статья сравнивает 'prop-drilling'(считается антипаттерном) и 'pub-sub' подходы. В Angular проектах(и вообще FE-мире ) последствия активного использования 'prop-drilling' достаточно хорошо известны. В Angular такого рода проблемы решают через возможности DI и RxJs(можно сигналы).
Если автор еще поизучает проблемы фронтенда, то он поймет, что лучше всего подписываться не на конкретные ивенты, а иметь единый источник истины в виде хранилища данных и реагировать на изменения конкретных значений в этом хранилище. И окажется, что некто Дэн Абрамов еще десять лет назад про это подумал и создал библиотеку Redux конкретно, чтобы решить эту проблему)
Я поддерживаю критику ангуляра, но не в этом случае, автор просто не умеет им пользоваться.
Поздравляю! Вы изобрели effector!
Фронтенд — новый легаси: Как мы проспали event-driven революцию