Pull to refresh

Comments 36

Кто-то использовал RethinkDB в качестве Event Sourcing, как замена стека БД и брокер сообщений?
Клиентское приложение, которое обновляет агрегат, а затем сразу же делает запрос с использованием представлений, может увидеть предыдущую версию агрегата. Поэтому приложение должно быть написано таким образом, чтобы не допустить получения пользователем этого потенциального несоответствия.

Хорошие результаты показывают:


  • возвращение представление агрегата приложению в ответ на команду создания/обновления средствами сервиса агрегата
  • агрегирование представлений разных агрегатов на стороне приложения

При таком подходе приложение гарантированно отдаёт пользователю агрегированное представление с учётом его действий.


Другой вариант:


  • возвращать в ответ на команду монотонно возрастающий идентификатор последнего события (или его вообще приложение создаёт), учтенного при обработке команды, или версию агрегата, или ещё какой-то монотонно увеличивающийся признак типа таймстампа или количества событий
  • сервис агрегированных представлений добавляет к агрегатам последний известный ему идентификатор событий агрегатов или версию

При таком подходе приложение отправляет команду, запоминает версию агрегата, если команда выполнилась, идёт за представлением нескольких агрегатов и проверяет версии тех, которые он изменял. Если версия меньше, то либо перечитывает, либо как-то сигнализирует пользователю, что данные устаревшие.

Мы пытались прикрутить идею CQRS в нашем проекте. Задача стоит такая:
— «обслуживать» текущий процесс
— именть возможность поднять состояние системы в прошлом (то есть пользователь говорит: хочу увидеть состояние системы как 1 месяц, 3 дня, 4 часа и 5 минут назад).

Мысль была — не заипсывать сущности в рдбмс, а записывать собственно ивентами. Пришли к идее cqrs.
Но и обломались:
1. нет ничего хоть сколь-нибудь готового
2. для подъёма системы «месяц назад» надо, грубо говоря, проиграть «от начала времени» до «месяц назад» и воссоздать объекты — как/кто — неясно
3. изменение модели данных неясно, как обрабатывать, когда система уже в глубоко использовании
UFO just landed and posted this here
Мы (пока) остановились на снепшотах. Но тогда смысл cqrs отпадает (для нас)
UFO just landed and posted this here
  1. Сильно зависит от уже используемого стека и возможности его сменить.


  2. Все сеттеры и иные мутаторы объекта преобразуются в добавление событий, все геттеры в проигрыватели с необязательным параметром типа таймстампа. Плохо только если у объекта были публичные свойства, с которыми работали все кому не лень, а стек (например PHP) не позволяет их малой кровью заменить на геттеры/сеттеры, тгда нужно городить фабрики с таймстампом и формировать объекты.


  3. Если всё получилось удачно инкапсулировать, то проблемы могут быть только с быстродействием, которые обходятся снэпшотами, и с потребляемой памятью.
Для (2) не нашлось ничего готового (C# с пропертями, но некритично, можно и поменять на яву).

Но всё равно проблема остаётся с изменением модели данных. «Старые события» могут быть несовместимыми с обновлёной моделью данных. Необходимо обновлять «старые события», но, т.к. события не привязаны к данным (в «стиле» рдбмс — ключи, юникнесс, т.п.), то обновления могут привести к значительной порче данных.
UFO just landed and posted this here

Сделать какой-то абстрактный класс события с минимально необходимым набором полей типа идентификатор и таймстамп, а уж от него наследовать старые и новые события, храня их в базе в каком-то виде, используемом для хранения наследования. В "проигрывателе" же выбирать обработчик для конкретного класса.

Реализация истории заказов с помощью MongoDB документа клиента, содержащего все его заказы.


А разве история заказов не укладывается в реляционную модель?
И вообще вся структура Клиент-Заказ-Товар.
Зачем NoSql тут?
Какие дает преимущества?

На реляционную модель истории ложатся плохо. Реляционная модель подразумевает что в выборках используются актуальные значения записей, связанных первичными/внешними ключами. Клиент сменил фамилию, изменилась цена товара и в выборке по заказам в реляционной модели должны быть новые значения по ключам, что обычно сильно противоречит бизнес-логике, требующей фиксации данных на момент какого-то бизнес-события типа выставления счёта на оплату заказа.


Тут не столько у NoSql явные преимущества, сколько преимущества Sql практически не используются. То есть ситуация "можно без проблем использовать NoSql СУБД, если вам важны их общие преимущества типа гораздо большей легкости горизонтального масштабирования".

Как-то Вы странно представляете себе реляционную модель…
Если цена меняется во времени, об этом есть записи.
Клиент сменил фамилию, об этом тоже есть запись. В Вашей модели его история потеряется
И речь шла не о преимуществах SQL, а как раз о недостатках NoSQL для представления определенных структур данных
UFO just landed and posted this here
Какие-то ненормализованные «таблы» получаются. В таком случае да, разницы нет, как хранить
все это добро

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

UFO just landed and posted this here
Вы это серьезно?
На RDBMS это невозможно.
UFO just landed and posted this here
Вы троллите так?
  create table [dbo].[order]
  (
	[id] [int] primary key clustered,
	[customerId] [int] not null foreign key references [dbo].[customer]([id]),
	[deliveryAddressId] [int] not null foreign key references [dbo].[address]([id]),
	[date] [datetime] not null
  );

  create table [dbo].[orderItem]
  (
	[orderId] [int] not null foreign key references [dbo].[order]([id]),
	[itemId] [int] not null foreign key references [dbo].[goods]([id]),
	[price] [money] not null
  );

Обвязку из [customer], [address], [customerHistory] и прочего сами додумаете?
Адресов на клиента будет в среднем 1.01, имен — 1.0000000000001
Запрос тоже написать или основы поизучаете?
И да, если у Вас на таком join'е все заткнется, научите, как такого достичь?

Про историю самое интересное. С запросом на обновление данных товара(включая название и категорию, а не только цену, а других данных в товаре и нет)/клиента(включая телефоны для связи, которые некоторые меняют чуть ли не раз в месяц) и запросом на выборку заказа с актуальными на date данными по клиенту и товарам.

Оно возможно, но никаких особых преимуществ буква R не даёт. Записи заказа должны будут дублировать все значимые для них поля записей клиента и товаров, ссылаясь на них лишь для поддержания ссылочной целостности для каких-то аналитических задач типа подсчёта количества заказов у клиента. Либо должна в том или ином виде поддерживаться полная история история изменений записей клиентов и товаров, что опять же приводит к массовому дублированию данных при версионировании посредством снэпшотов, или сложным механизмам "наложения патчей"

Вы бы знали, сколько подобной каши я повидал…
Но дело Ваше. Чем больше пионэров, тем больше моя ценность, как специалиста.
/* Это сарказм и ирония, если что */
UFO just landed and posted this here

Отличная статья, большое спасибо! В закладки!
Поговорить о деталях видимо может быть уместным только попробовав на практике… Пока у меня небыло подобного опыта в полном обьеме хотя писать эвенты вместа апдейта статуса уже догадывались под разным причинам ;)


Интересует такой момент. В описаной вамеи связке Event Sourcing + CORS события имеет смысл класть в какое-то хранилище (Event Bus).
Вы вначале статьи написали о Кафке и похоже именно на неё намекаете, (я именно в таком контексте слышал о кафке). Могли бы вы побольше пролить свет о роли кафки или другого хранилища?
И правильно ли я понял что при наличии надежного хранилища событий, в идеале его можно сделать единственным и эксклюзивным хранилищем всей истории событий?
Даный подход открывает невероятное возможности в динамике конзумирующих сервисов в таком случае, что очень интересно.


П.С.
А при переходе на таку модель с чего-то исторического, надобно существующий статус как-то перевести в начальные события, что-бы отчего-то оталкиватся, без потери информации и возможнои истории, что может быть тоже не тривиально совершенно..

Сделать событие LegacyImport, которое игнорирует подавляющее большинство бизнес-правил, а тупо заполняет структуры данных.

Я об этом же. Но это не тивирально может быть. Например если уже есть некая история.
P.S. У вас с Кафкой опыт есть? Я так понимаю именно для это-го он подходит?

Может, конечно, быть что-то нетривиальное. Но, по идее, хорошо зная систему, можно если не реальную последовательность событий восстановить, то, хотя бы, приводящую к текущему состоянию.


Нет, я сделал подобный переход чисто на SQL, как на хранилище событий с частично полным импортом снэпшота как начала истории, частично с эмуляцией истории (включая события по расписанию, которые нигде не регистрировалисб).

Ну самое тривиальное упращение: берем актуальный в каую-то секунду статус из базы данных и импортируем в новую ситему. Теоритичеки тривиально…
А на практике новая система может быть вовсе не понимать SQL и надо думать о трансформации и реализовывать её, причем может надо избежать даунтайм.
Далее одна система с которой мне приходится возится, несмотря на то что она достаточно легаси, построена на нескольких базах данных которые несут распределенно статус и историю. Некоторые пишут по 2 миллиона событий в день. Одна только синхронизация такого експорта/импрота в новую систему не тривиальна как мне представляется из опыта.
Вы работаете с SQL это конечно упрощает но в случае вышеупомянутой системы, было бы в нашем случае не целесобразно, потому что железо для такой SQL машины стоило бы непомерно… Да и зачем когда вся фишка вроде как в специализированых решения как кафка. Мне бы очень хотелось пощупать это ;)


Не зависимо от импорта, если я дальше думаю о вышеупомянутой системе, присутствует условие стирания событий назовем их Messages (законы просто не позволяют долго хронить определнню информацию) и в даже в новой системе эти события придется стирать, что не предусмотренно в описанном подходе в принципе если я правильно понимаю.

А на практике новая система может быть вовсе не понимать SQL и надо думать о трансформации и реализовывать её, причем может надо избежать даунтайм.

Я избегаю резких переходов, когда меняются много факторов одновременно, предпочитаю инкрементные: или сначала переводим на новую модель, используя старое хранилище, или сначала переводим на новое хранилище, используя старую модель.


Да и зачем когда вся фишка вроде как в специализированых решения как кафка.

Как я понимаю, кафка — это брокер сообщений, но не их хранилище в рамках ивент-соурсинг систем.


и в даже в новой системе эти события придется стирать, что не предусмотренно в описанном подходе в принципе если я правильно понимаю.

В посте описан механизм снэпшотов для увеличения производительности, мы их тоже используем именно в этих целях, переводя записи событий до последнего снэпшота в отдельную архивную базу, чтобы в исключительных случаях смотреть всю историю за все годы работы, но вполне можно и в целях выполнения требований регулятора просто их удалять.

Кафка подходит. Хотя на мой вкус немного радикальное решение но этим то и интересно.
Вот тут очень хорошо описано с поянениями в каментах:

Read operations like list all users, fetch a specific user, etc will retrieve data directly from the read store(postgresDB).

I can’t read all events of a single entity without reading the whole kafka partition.

Вот как-то настораживает. Как по мне, то Кафка тут именно как брокер сообщений используется, пускай и с персистентностью для неизветсных ещё даже потребителей, из которого довольно дорогой ценой можно извлечь историю событий конкретной сущности, а постгри как основное хранилище.

В том примере Постгри какраз второстепенен. Ничего не попадет ни в какие базы данных никаких серверов кроме как через кафку. И кафка единственнай правда остальное получается снэпшоты и кеши.

Попадать оно может через Кафку, но если команды отрабатывают, читая данные из Постгри, а чтение полной истории сущности из Кафки или вообще не осуществляется, или только в каких-то исключительных случаях происходит, то у меня язык не поворачивается назвать его второстепенным. Заменить Кафку на тот же Раббит с удалением события после его чтения и реально мало что изменится, кроме некоторых фич типа возможности подключить новый обработчик событий, который прочтёт и обработает все события с момента запуска, или возможности восстановить базу Постгри с нуля по событиям.

Может быть мы о разном. Давайте разбираться…
Все серивисы подписаны на кафку и имеют локальные копи (снэпшоты, кеши и т.д.) так как им удобно… Они могут вести свои собственные базы данных NoSQL и прочую организацию для исполнения задачи. Им не нужно совершенно прочесывать кавку.
Кавка остается "универсальной правдой" потоком событий (или фактов). Сервисы подписаны на кафку и лишь слушают изменения и актуализируют свое сотояние.
Допустим у вас 250 сервисов… я не совсем понял как они слушают и кого? Раббита? или постоянно ходят в базу данных что-бы забрать новые эвенты? Не говороя уже о не скалиремости релациональной БД не понимаю даже приемущества ДБ+раббит супротив кавки начиная с систем не помещающихся на 3 хоста

Поэтому приложение должно быть написано таким образом, чтобы не допустить получения пользователем этого потенциального несоответствия.

Я нашел эту статью как раз по запросу как писать такие приложения, жаль что тут ответа нет.
Пока что, у меня ощущения что в распределённых приложениях невозвоможно добится 100% консистентности даже eventual
Sign up to leave a comment.