Обработка событий в кросс-агрегатных отношениях и агрегированном состоянии - PullRequest
0 голосов
/ 26 октября 2018

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

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

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

Агрегаты раскрывают действия, которые фактически выдают события. Например, company.Create(firmName, address, taxid, ...) выдает событие CompanyCreated и применяет его к себе. Когда задание заканчивается, все события из всех агрегатов, загруженных в контексте этого задания, собираются и сохраняются в хранилище событий.

Теперь я попал в ситуацию, которая, я уверен, очень распространена, когда у меня есть отношения между агрегатами. Например, Customer имеет Contacts, или SupportAgent является членом Department. Это агрегаты в моем дизайне.

Давайте рассмотрим пример Department. Состояние Department состоит из заголовка, описания, некоторых других свойств и списка идентификаторов SupportAgent тех агентов, которые являются членами этого отдела. Состояние SupportAgent состоит из имени, фамилии, номера телефона, адреса электронной почты, ... и списка Department идентификаторов тех отделов, членом которых является этот агент.

Теперь, когда обрабатывается команда типа AddAgentToDepartment(agentId, departmentId), выдается два события. DepartmentAdded выдается для соответствующего агента, который добавляет идентификатор отдела в состояние агента, а SupportAgentAdded выдается для соответствующего отдела, который добавляет идентификатор агента в состояние отдела.

Мой первый вопрос: Правильно ли сохранять идентификаторы связанных агрегатов в состоянии агрегата? Под «правильным» я имею в виду, что это лучшая практика? Или есть другой способ (например, сохранить отношения в виде сущности / агрегата DepartmentMemberManager или чего-то в этом роде. На самом деле эта сущность или что-то вроде единственного здесь. Есть ли такая вещь в мире DDD)?

Моя другая мысль о воспроизведении событий. В предыдущем примере генерируются два события, но для обновления представлений необходимо обработать только одно из них, поскольку оба события описывают один и тот же переход в состоянии системы (агент и отдел связаны). Я выбираю обработать только событие SupportAgentAdded для обновления просмотров. Мой обработчик событий выполняет сценарий SQL, чтобы обновить соответствующие таблицы базы данных, чтобы отразить текущее состояние системы.

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

Если вы являетесь экспертом по DDD и ES или, по крайней мере, у вас есть опыт, я хотел бы получить некоторые подсказки о том, что вы видите, я делаю или думаю, что неправильно, и в каком направлении мне смотреть.

Ответы [ 3 ]

0 голосов
/ 26 октября 2018

В моем проекте у меня есть обработчики команд, которые принимают команду, запускают задание (единицу работы) и загружают необходимые агрегаты из хранилища агрегатов (которое загружает агрегаты из хранилища событий путем воспроизведения событий).и они манипулируют агрегатами с помощью открытых действий каждого агрегата, а затем закрывают задание.Изменение нескольких агрегатов в рамках одной транзакции (единицы работы) становится действительно сложным, когда агрегаты хранятся в разных местах.Если все находится в «одной базе данных», вы можете сойти с рук.Но как только вы вводите вторую базу данных, вы фактически вводите «распределенную транзакцию», с которой гораздо неудобнее иметь дело.

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

Что происходит вВ случае, если нам нужно воспроизвести некоторые события, чтобы привести только определенное представление совокупности в согласованное состояние?

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

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

Правильно ли частично воспроизвести некоторые события или все события вхранилище событий должно быть воспроизведено, чтобы привести всю систему в согласованное состояние?

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

Вы можете найтиполезно рассмотреть этот доклад 2014 Грега Янга

0 голосов
/ 01 ноября 2018

1) Я думаю, что ваша модель не имитирует домен.Например: Вы называете команду ('AddAgentToDepartment') на основе соглашения CRUD, а не процесс бизнес-домена, который может быть в этом случае либо назначением агента для отдела, либо назначением отдела для агента.

2) Кто является диспетчером / менеджером / привратником в этой ситуации?Несет ли департамент ответственность за соблюдение всех бизнес-правил при назначении агента?Или это ответственность агентов, чтобы выбрать департамент и убедиться, что он соответствует определенным правилам бизнеса?

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

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

0 голосов
/ 26 октября 2018

CQRS означает разделение ответственности команд-запросов. Есть две стороны C - команда, сторона записи. Q - Query, Read side.

Агрегаты живут на стороне C-команды и могут выполнять только команду. Агрегаты не могут быть запрошены. Так что в вашем примере обработчик команд вашего агента просто не может общаться с каким-либо агрегатом отдела

Хотя модель чтения может быть запрошена, так что ничто не мешает вам запрашивать модель чтения некоторых департаментов. Но есть проблема согласованности.

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

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

Теперь агрегат не является сущностью. Само название «агрегат» подразумевает, что там есть несколько «вещей». Агрегат - это объект, который может выполнять команды и обеспечивать бизнес-правила. Это означает, что команда отправляется одному агрегату.

Выбор агрегатов является основным видом деятельности в системе CQRS / ES. Ошибки очень дороги, потому что вам придется иметь дело с версионированием и рефакторингом событий (Грег Янг недавно написал книгу об этом)

Итак, в вашем примере у нас есть одна команда:

AddAgentToDepartment(agentId, departmentId)

Первый вопрос - к какому агрегату он адресован? Помните - одна команда для одного агрегата. Это дизайнерское решение, которое зависит от вашей системы. Я бы подумал о таких вещах, как: агент все еще может быть агентом без этой команды? Наверное, завтра у вас не будет отделов, но, скажем, Продукты и Агент не должны быть затронуты. Может ли отдел быть отделом без этой команды? Маловероятно - это вещь для групповых агентов. Таким образом, я бы сделал Департамент агрегатом, который получает

AddAgentToDepartment(departmentId, params: { agentIdToAdd })

И отдел отдела будет заботиться о бизнес-правилах (нельзя добавить одного и того же агента дважды, не может удалить несуществующего агента и т. Д.)

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

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

Мой первый вопрос: правильно ли сохранять идентификаторы связанных агрегатов в состоянии агрегата?

Нет. Команда отправляется одному агрегату, а обработчик команд может иметь дело только с состоянием агрегата, которое вычисляется из потока событий этого агрегата. Хранение идентификаторов других агрегатов не поможет, потому что вы не можете их нигде использовать.

Моя другая мысль о воспроизведении событий. В предыдущем примере генерируются два события, но для обновления представлений необходимо обработать только одно из них, поскольку оба события описывают один и тот же переход в состоянии системы (агент и отдел связаны).

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

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

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

На стороне 'C' - команда (запись) после получения команды состояние агрегата восстанавливается из потока событий этого агрегата путем запроса хранилища событий: дать мне все события для агрегата 12345.

На стороне «Q» - запрос (Чтение) нет агрегатов, есть модели чтения. Эти модели чтения обычно создаются из событий разных типов для разныхагрегаты. Когда вам нужно перестроить модель чтения - вы запрашиваете хранилище событий: дайте мне все события, которые соответствуют моим критериям. Затем вы применяете эти события для чтения модели (это может занять некоторое время), и когда модель чтения работает-на сегодняшний день он может подписаться на текущий поток событий и обновлять себя в режиме реального времени.

...