Комментарии 26
Ссылка на репо 404.
Я пожалуй никогда толком не понимал event driven. В моем понимании нельзя выложить доступ к брокеру сообщений в открытый доступ. Обязательно должен быть фасад (например тот же самый rest api).
Далее, все эти брокеры мне кажутся во первых менее надежными, потому что в них нет транзакций, да и в целом вопросы к тому, как они хранят данные. Во вторых внутри них нет языка запросов, чтобы что-то найти в случае ошибки или некорректного поведения в рамках бизнес процесса. То есть нельзя просто взять и вывалить все это юзеру на ui, чтобы он там искал свои загруженные или потерянные заказы.
В итоге я прихожу к другим паттернам - заказы мы складываем в БД, юзеру мы показываем на ui из БД со всякими фильтрами (с использованием языка запросов). Далее заказ у нас имеет статусную модель и конечный автомат. И прежде чем что-то отправлять в брокер сообщений - надо отразить это в БД (transaction outbox).
При этом есть статьи от того же Яндекса как они делали очередь на postgresql.
В итоге event driven и брокеры сообщений в частности - это такая штуковина сбоку, которые сами по себе работать не будут. Мало того, в aws cloud есть event bridge - тоже что-то вроде брокера сообщений, но микросервисы он дергает по rest api 🙃
В общем, вывод - оригинальная статья довольно поверхностная и нет оговорок про те моменты, которые подсветил я.
нельзя выложить доступ к брокеру сообщений в открытый доступ
Конечно. Это только внутреннее хозяйство.
Между доменами лучше тот же outbox + брокер, просто контракт строже: версионирование событий, схемы, обратная совместимость.
Или синхронный вызов с ретраями и идемпотентностью на стороне получателя. Проще в отладке, хуже в отказоустойчивости.
Гарантии в обоих случаях строишь сам: брокер даёт at-least-once доставку, но не бизнес-консистентность. Она через идемпотентность, saga, или eventual consistency с компенсациями.
Ссылка на репо 404.
Поправил
брокеры мне кажутся во первых менее надежными, потому что в них нет транзакций
Есть гарантии at least once, что можно привести к транзакции
at-least-once + идемпотентность = effectively-once. Это эквивалент транзакционной семантики по результату
По-моему гарантии доставки и первичное сохранение сообщения без потери - это немного разные вещи. Грубо говоря - что если сервер с брокером перезагрузится, потеряются ли входящие сообщения?
Первичное сохранение сообщения - это процесс между продюсером и брокером. Пока мы точно не убедились, что брокер получил сообщение, то ни о каком exactly-once / at-least-once речи быть еще не может. Это как бы вторая часть процесса - между самим брокером и консьюмером.
Следовательно там надо смотреть настройки и возможности конкретного брокера - есть ли write ahead log, бэкапы и все такое, что для реляционных БД являются общепринятыми вещами. Ну и очевидно, что если брокер пишет каждое новое сообщение в ahead log и сразу на диск, чтобы их не потерять - он не будет в разы быстрее БД, потому что механизм и задержки примерно одинаковые.
Во вторых внутри них нет языка запросов, чтобы что-то найти в случае ошибки или некорректного поведения в рамках бизнес процесса. То есть нельзя просто взять и вывалить все это юзеру на ui, чтобы он там искал свои загруженные или потерянные заказы.
Типичный случай для применения CQRS - подключаем к потоку событий сервис, который их агрегирует в представление, к которому удобно делать запросы. Например, в реляционную БД.
В итоге я прихожу к другим паттернам - заказы мы складываем в БД, юзеру мы показываем на ui из БД со всякими фильтрами (с использованием языка запросов). Далее заказ у нас имеет статусную модель и конечный автомат. И прежде чем что-то отправлять в брокер сообщений - надо отразить это в БД (transaction outbox).
Event sourcing как раз про то, чтобы сделать первичным источником информации не СУБД, а поток событий. Состояние БД в этом подходе вторично, это некая агрегация потока.
Да, EDA - это сложно.
Таки не вопрос, пускай сервис после брокера. Пускай поток событий - первичный источник. Вопрос в другом - пришел юзер и спросил "у меня тут состояние в БД не соответствует ожидаемому, почему?" И теперь надо пойти и найти где произошел сбой - это событие вообще не пришло? Или пришло, но брокер его не сохранил? Или сохранил и оно там зависло или попало в dead очередь? Или что-нибудь еще? И вот чтобы найти (или не найти) это одно конкретное сообщение среди миллионов других в брокере - это невозможно сделать без языка запросов, которого в брокерах нет.
В event sourcing, если брокер сохранил событие - система считает, что оно было, рано или поздно его эффекты распространятся на все производные БД, а в случае сбоев его можно повторно прочитать из лога событий, и таким образом восстановить состояние БД (это автоматически происходит). Если брокер не сохранил - значит и не было события, и БД его эффектов видеть не может. Обычно используется kafka, которая является не совсем брокером, а распределённым логом событий, своего рода БД, и хранит события "вечно".
По этой идее, даже при сбоях состояние БД должно автоматически синхронизироваться с потоком событий. А если какая-то аномалия (состояние не синхронизировалось правильно) - нужно перезапускать построение БД с начала или со снэпшота.
Где пропало сообщение, где что-то пошло не так - это ищется по логам сервисов, распределённой трассировкой и т.п. сложными способами. Но в этом подходе событие, будучи записанным на диск, не пропадает, а вот его эффекты могут появляться не сразу, и даже иногда временно исчезать (например при перестроении БД после сбоя, при котором БД была утеряна).
А для новых представлений нужно накатывать всю историю сообщений? Сколько это займет времени, неделю? На проектах такие представления могут возникать по несколько раз в день в виде sql запросов.
Баланс снапшоты часто/редко
Вычисление n+1 состояния из n обычно много быстрее чем запрос к БД, вычисление n=>n+1 обычно in memory и потому быстрое. Если нет, то надо что-то менять.
Да, накатывать всю историю. По сути представление - это аналог индекса в реляционной БД (а часто их и содержит, т.к. это хороший инструмент для запросов). Новый индекс можно построить, обработав все данные в таблице. Ну или не строить, а пытаться выцепить что-то из потока полным перебором. И то, и другое - долго.
В той же kafka есть механизм log compaction, который позволяет терять неактуальные события (например, старые значения параметра, когда было установлено новое). Это может ускорять построение/перестройку представлений.
Нет, индекс накатывается локально и на текущий срез данных, а представление строится удалённо и по всей истории. Плюс индекс работает на множество запросов, а представление на сложную выборку нужно отдельное. Так что индекс строится гораздо быстрее, плюс мне не требуется индекс на каждый запрос...
Сколько будет строится представление в масштабах какого-нибудь твиттера? А на реальном проекте таких представлений нужно несколько в день. Лог компакшн будет только мешать, т.к. представление обрабатывает бизнес события, которые будут потеряны.
И это только часть проблемы. Как часто в реальном проекте меняется структура бд? Почти каждый день. А как изменять события? Вводить версионирование и поддерживать в каждом агрегате и представлении все предыдущие версии? Это же в какой ад превратится код, чего данные практики призваны избегать.
Плюс индекс работает на множество запросов, а представление на сложную выборку нужно отдельное.
Производные данные (представление) могут содержать все требуемые индексы (~по каждому полю), строятся буквально вызовами create or insert ... для каждого события, а потом (или перед вставками) create index ... ; create index ... ; ... Тогда, скорее всего, делать полное чтение событий на каждый новый запрос не придётся.
Сколько будет строится представление в масштабах какого-нибудь твиттера?
Долго. С другой стороны, какая РСУБД используется в масштабах твиттера?
Это же в какой ад превратится код, чего данные практики призваны избегать.
Данные практики повышают производительность и чтения, и записи, имеют очень высокую горизонтальную масштабируемость, но адовость кода также повышается. По сути, если добавлять все эти индексы, обратную совместимость записей, в идеале это будет ре-имплементация функциональности РСУБД типа постгреса, но с нюансами. Нужно иметь весомые причины, чтобы начать реализовывать свой "постгрес". В большинстве случаев лучше взять готовый.
Event-driven обычно применяется не в джейсоноукладке, там нет никаких пользователей, а часто — и баз.
нельзя выложить доступ к брокеру сообщений в открытый доступ. Обязательно должен быть фасад
О да. Удачи написать фасад, который не станет бутылочным горлышком при мало-мальски большой нагрузке.
внутри них нет языка запросов
Ага, а моя кофеварка не умеет жарить блины. Это брокер, какие запросы еще?
там нет никаких пользователей
Эмм, в смысле нет пользователей? Весь софт пишется для тех или иных пользователей.
Удачи написать фасад, который не станет бутылочным горлышком при мало-мальски большой нагрузке.
Серьезно? Вы публикуете ip адрес брокера наружу без прослоек и даете любому внешнему пользователю подключатся к нему напрямую?
Это брокер, какие запросы еще?
Такие, чтобы ответить на вопрос "где и почему сломалось"
Автор забыл, что месседж драйвер девелопмент был задолго до микросервисов, и зачем-то пишет плюсы и минусы микросервисов, и то с ошибками. Например про асинхронность, тесную связанность и т.д. Сам же MDD не был хорошей практикой никогда, в первую очередь за свою неадекватную сложность и стоимость, которая при этих его параметрах проигрывала стандартным протоколам взаимодействия, тому же soap. Тяжело спроектировать и собрать за адекватные деньги даже монолит на MDD, не то что микросервисы, которые сами по себе сомнительный перегрженный паттерн не для каждого случая применения.
Так как статья - перевод, ответы от переводчика не ожидаются. Удачи с бездумным механическими "переводами"
Не соглашусь, что асинхронность нет в REST. Потребитель вызывает регистрационный метод продюсера, в котором передает адрес своего on-event интерфейса. Далее, когда событие произошло, продюсер дёргает этот REST-интерфейс потребителя, в котором передаёт событие. Да, тут много недостатков, но асинхронность налицо. Классический callback.
Не соглашусь, что асинхронность нет в REST. Потребитель вызывает регистрационный метод продюсера, в котором передает адрес своего on-event интерфейса. Далее, когда событие произошло, продюсер дёргает этот REST-интерфейс потребителя, в котором передаёт событие. Да, тут много недостатков, но асинхронность налицо. Классический callback
Что? У клиента нет рест интерфейса. Может, речь про микросервисы?
Как же нет, если мы рассматриваем случай с REST?
Входящего рест интерфейса у клиента нет
Кто это определил? Я говорю, что есть. Сделаем. И делают.

Лучшие практики для событийно-ориентированной микросервисной архитектуры