gzhegow
@gzhegow
aka "ОбнимиБизнесмена"

CQRS/ES (или это EventDriven вообще) — Кто поможет разобраться с терминами?

* Пожалуйста не удаляйте тег PHP. Потому что будут одни бизнес-аналитики, а мне мнение программистов тоже важно.

Взялся для тестирования себя реализовывать CQRS/ES вместе. CQRS тут все вроде просто, и статья есть хорошая - дескать делаем два диспетчера/роутера, вешаем на имя класса обработчик, создаем его контейнером, плодим классы обработчиков и классы данных.

С ЭвентСорсингом сложнее. Здесь у нас есть ивент-стор и даже если какая-то библиотека дает метод listen() по факту это все равно частотный опросник, который может:
- получить несколько сообщений из стора/очереди
- попробовать несколько раз
- повисеть на линии столько-то сек (или висеть пока не придет указанное число сообщений)
- подождать между повторными подключениями

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

Я хочу понять flow как оно работает. До сих пор я считал, что там принцип не сложнее обычного радиообмена по рации - клиент шлет запрос "сделай", получает ответ "принято", через некоторое время спрашивает "а сделал?", получает ответ "готово", "сломалось", "еще делаю", "еще даже не начал"

Но я поплыл в месте разделения ответственностей на команды и ивенты и кто что может эмитить.

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

Итак
1) клиент - "сделай заказ", (command)
2) апи - "принято" (response)
3) воркер - "взял в обработку" (command-handler)
4) воркер - "сделано" (? - вроде ивент, но когда цепочка действий для одной команды - непонятно)
5) клиент - "а сделано?" (query)
6) апи - "да" (response)

Вопросы следующие:
1. Команда вроде как юзер-инпут и только. И команды требуют очередности действий. То есть их не параллелят на уровне "берем 10 команд и делаем одновременно", их параллелят через "берем десять сторов с командами, из каждой по одной, и делаем параллельно", и то если возможно - читай - на одной машине больше одного куска приложения, например "заказы" и "социалочка" - у каждой своя очередь, в целом одно на другое не влияет (кажется). Но если цепочка команд? Воркер делает работу, понимает что чего-то не хватает, он должен сам запустить (заэмитить в стор) новую команду, позволяющую выполнить текущую? Как он поймет, что может продолжать, и в конце концов - он же не должен висеть все время пока условия будут выполнены (то есть висит сама джоба, но воркер пошел другую работу делать, значит хендлер должен чем-то закончится, при этом изменив статус джобы на условно "докопал_до_сюда", а второй раз уже должен "докопал_до_обеда", значит он вроде как должен отчитаться, что сделал "всё что мог", но это не означает, что "задача выполнена".
В контексте ситуации - команда может эмитить другие команды или только события-отчеты? И если события отчеты, то кто управляет потоком всей команды в целом, когда она состоит из нескольких действий? В парадигме CQRS команда ещё и ничего не возвращает, хотя есть Янг, который считает что возвращает (ну промиз из 10 команд как бы может вернуть 10 резолвов)

2. ивенты. обработка ивентов чаще всего вешается на параллель типа "берем стор, хаваем десяток, и запускаем 10 обработчиков", т.к. каждый обработчик будет отвечать за свое действие и они друг другу не мешают. И вот тут важно... или так не делают? То есть может ивенты в ES это как раз и контроль очередности, условно их метка времени и вызвавшая их команда заставляют точно так же брать их по одному и пока не обработается - не брать следующий? Но это тоже странно, ведь ивент "создан заказ" явно может в параллельке выполнить действия "отправить в отчет за месяц", "отослать письмо", "записать в базу, эластик и куда-то еще", и это явно непоследовательно должно делаться, ведь долго будет. Но подымая пункт один получается, что на этот ивент могут быть подписаны другие действия, которые в целом могут поломать порядок выполнения? если та же команда сказала, что в "одиночку справится не может", то есть требует другую команду завершенной, то команда отчиталась ивентом и эти "другие действия" выполнятся в параллельке, что может нарушить понятие "ивент не имеет сайд-эффектов", то есть выходит что через ивенты цепочки команд выполнять нельзя. Но тогда как их выполнять?
Дополню, что может правильно это "брать один ивент, но параллелить выполнение всех обработчиков" (т.к. на одном ивенте их может быть десяток), вместо "брать 10 ивентов и параллельно выполнять в 10 потоков, каждый из которых выполняет свои обработчики последовательно"

3. нашел информацию, что команда это "только юзер инпут", а ивент "любой отчет". Но понимая идею цепочек, вроде как команда может создать другую команду, значит команда уже не только юзер инпут, а в принципе "задача". Тут коллизия получается на уровне "команда в CQRS" и "команда в ES" - это разные ответственности?

Помогите встать на ноги, явно плыву...
  • Вопрос задан
  • 218 просмотров
Пригласить эксперта
Ответы на вопрос 2
Maksclub
@Maksclub Куратор тега PHP
maksfedorov.ru
CQRS — разделяем чтение и запись, в идеале и стораджи (в одно пишем результат команд на write сценарии), с др читаем проекции, готовые данные

Между этими сценариями работают какие-то воркеры, которые готовят чтение...

Зачем это делают: разделение ответственности, о котором в названии паттерна указано, оптимизация чтения
Очевидно, что для чтения можно оптимизировать хранилище — например NoSQL с готовыми данными

Тк чтение и запись разделены — нет смысла на записи делать изменение стейта по классике, когда мутируем состояние в сторадже и можно просто хранить поток событий, проматывая которые мы получим стейт бизнес-сущностей (они на чтении уже не участвуют и нет потребности в "готовом" измененном стейте)... Это очень удобно,

Так у нас появляется ES, появляется контроль изменений и гибкость структуры данных, когда может много чего контролировать...

На эти события вешаем те самые воркеры, которые инициируют подготовку данных для чтения (или нет)
Ответ написан
gzhegow
@gzhegow Автор вопроса
aka "ОбнимиБизнесмена"
Вопрос все еще открыт.

Из того к чему пришел опрашивая несколько человек (среди них два архитектора, два сисадмина и два программиста):

- в идеи нет никакой магии. простейшая реализация - закидываем код проекта в github, запускаем его на двух машинах вместо одной, просто вместо http-роутера мы используем message-брокер + eventbus, и соотственно каждая из машин обслуживает свои роуты. придумывать "ивенты" которые продвигают рабочий процесс, где один ивент является началом другого рабочего процесса - сулит головняк, такой же как если бы мы в пхп не использовали классы-сервисы, а вообще всё и всегда делали на ивентах. задолбались бы и запутались кто начало кто конец.

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

- брокеры сообщений. краткий обзор их такой - они работают всегда в одном из трех режимов. первый - сообщение:
1) "точка-точка" - "я отослал, ты придешь - получишь".
2) "трансляция" - я вещаю, кто хочет слушает, кто не пришел - пропустил
3) "трансляция-точка" - я вещаю, все слушают, кто не пришел - получит когда подключится.
Команды работают в режиме "трансляция-точка", ивенты "трансляция", уведомления "точка-точка".
Если бы мы делали хранение истории транзакций - то здесь ивенты должны работать в "трансляция-точка", ведь нам нельзя потерять ни один из них, их надо в постоянный ящик сохранить. Ещё в брокерах есть понятие "подтверждение". Это про надежность. После получения сообщения если выставить режим работы "нужно подтверждать" - непрочитанное удаляется только тогда, когда его подтвердили, иначе запихивается обратно в канал, причем не "следующим", а по временной марке на свое место. Это нужно там где есть "входящая точка", и да, если команда вернула "готово" или "невозможно" - сообщение подтверждается, если команда вернула "перебой связи" и "что-то пошло не так" - сообщение не подтверждается.

- параллелизация. пока не понятно, какие проблемы вызовет параллель всего, попробую методом тыка. Когда мы использовали роутер - параллелизацией занимался nginx/fpm запуская десяток процессов. И если "обновить заказ" запускалось раньше чем "создать его", мы справедливо получали ошибку мол "невозможно" или "заказ не существует". Когда у нас есть брокер - то параллельно будут созданы "джобы" на первой машине, и их нужно резолвить на второй. Если мы дальше синхронно будем выполнять команды - это будет дольше, чем раньше, пользователей много, а поток всего один. Нам нужно их выполнять точно так же параллельно, только без nginx, а вручную, используя "ext-pcntl" для unix или "composer require react/async" для windows.

- не путать event-sourcing и event-driven-design (что я и сделал в начале топика). ивент сорсинг в широких кругах известен как "история транзакций". Что получается, когда мы все приложение делаем в режиме "история транзакций" - получается биткоин. Кто-то миллионер, а вся планета тратит электричество на расчет его данных и мощностей все равно не хватает. у вас есть своя планета с людьми и компьютерами? думаю, пока нет.

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

Поправляйте кто знает.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы