CQRS означает разделение ответственности команд-запросов. Есть две стороны C - команда, сторона записи. Q - Query, Read side.
Агрегаты живут на стороне C-команды и могут выполнять только команду. Агрегаты не могут быть запрошены. Так что в вашем примере обработчик команд вашего агента просто не может общаться с каким-либо агрегатом отдела
Хотя модель чтения может быть запрошена, так что ничто не мешает вам запрашивать модель чтения некоторых департаментов. Но есть проблема согласованности.
Экземпляр агрегата согласован в соответствии с его потоком событий, что означает, что ничто не может изменить состояние этого агрегата во время выполнения команды. Таким образом, ваш агрегат является границей транзакции - все в его состоянии согласованно, а все вне его состояния - возможно, несовместимо.
Так что, если вы имеете дело с чем-либо, находящимся за пределами агрегатного состояния - вы имеете дело с потенциально противоречивыми данными - в вашем примере ваш отдел может быть уже удален, но модель чтения еще не показывает это.
Теперь агрегат не является сущностью. Само название «агрегат» подразумевает, что там есть несколько «вещей». Агрегат - это объект, который может выполнять команды и обеспечивать бизнес-правила. Это означает, что команда отправляется одному агрегату.
Выбор агрегатов является основным видом деятельности в системе CQRS / ES. Ошибки очень дороги, потому что вам придется иметь дело с версионированием и рефакторингом событий (Грег Янг недавно написал книгу об этом)
Итак, в вашем примере у нас есть одна команда:
AddAgentToDepartment(agentId, departmentId)
Первый вопрос - к какому агрегату он адресован? Помните - одна команда для одного агрегата. Это дизайнерское решение, которое зависит от вашей системы. Я бы подумал о таких вещах, как: агент все еще может быть агентом без этой команды? Наверное, завтра у вас не будет отделов, но, скажем, Продукты и Агент не должны быть затронуты. Может ли отдел быть отделом без этой команды? Маловероятно - это вещь для групповых агентов. Таким образом, я бы сделал Департамент агрегатом, который получает
AddAgentToDepartment(departmentId, params: { agentIdToAdd })
И отдел отдела будет заботиться о бизнес-правилах (нельзя добавить одного и того же агента дважды, не может удалить несуществующего агента и т. Д.)
Помните, вы можете легко иметь модель чтения для агента, которая перечисляет все отделы для данного агента, вам просто не нужны отделы в состоянии агрегата агентов, потому что вы не будете отправлять агенту связанные с отделом команды .
В случае, когда все связанные с агентом команды должны знать об отделах, вы можете сделать Агента целью AddAgentToDepartment
. И агрегат отдела будет иметь минимальный набор команд: создать, переименовать, удалить.
Мой первый вопрос: правильно ли сохранять идентификаторы связанных агрегатов в состоянии агрегата?
Нет. Команда отправляется одному агрегату, а обработчик команд может иметь дело только с состоянием агрегата, которое вычисляется из потока событий этого агрегата. Хранение идентификаторов других агрегатов не поможет, потому что вы не можете их нигде использовать.
Моя другая мысль о воспроизведении событий. В предыдущем примере генерируются два события, но для обновления представлений необходимо обработать только одно из них, поскольку оба события описывают один и тот же переход в состоянии системы (агент и отдел связаны).
Ваш поток событий должен иметь смысл для эксперта по домену . В вашем примере, единственное событие AgentAddedToDepartment
имеет смысл. Два события - нет. В большинстве случаев одна команда должна генерировать одно событие.
Что происходит в случае, если нам нужно воспроизвести некоторые события, чтобы привести только определенный вид агрегата в согласованное состояние?В частности, когда я хочу воспроизвести события для агента поддержки, будут воспроизводиться только события DepartmentAdded
, и эти события никем не обрабатываются, поэтому представления не будут обновляться.Правильно ли частично воспроизвести некоторые события или все события в хранилище событий должны быть воспроизведены, чтобы привести всю систему в согласованное состояние?
Похоже, вы смешали сторону записи и чтения.Воспроизведение событий на одной стороне не должно влиять на другую сторону.Наша инфраструктура reSolve работает следующим образом:
На стороне 'C' - команда (запись) после получения команды состояние агрегата восстанавливается из потока событий этого агрегата путем запроса хранилища событий: дать мне все события для агрегата 12345.
На стороне «Q» - запрос (Чтение) нет агрегатов, есть модели чтения. Эти модели чтения обычно создаются из событий разных типов для разныхагрегаты. Когда вам нужно перестроить модель чтения - вы запрашиваете хранилище событий: дайте мне все события, которые соответствуют моим критериям. Затем вы применяете эти события для чтения модели (это может занять некоторое время), и когда модель чтения работает-на сегодняшний день он может подписаться на текущий поток событий и обновлять себя в режиме реального времени.