Я использую подход DDD/CQRS/ES, и у меня есть некоторые вопросы о моделировании моих агрегатов и запросов. В качестве примера рассмотрим следующий сценарий:Перестроить запросы из событий домена несколькими агрегатами
Пользователь может создать WorkItem, изменить его заголовок и связать с ним других пользователей. В WorkItem есть участники (ассоциированные пользователи), и участник может добавлять Actions в WorkItem. Участники могут выполнять действия.
Предположим, что пользователи уже созданы, и мне нужны только пользовательские идентификаторы.
У меня есть следующие WorkItem команды:
- CreateWorkItem
- ChangeTitle
- AddParticipant
- AddAction
- ExecuteAction
Эти команды должны быть идемпотентна, так что я не могу добавить дважды тот же пользователь или действие ион.
И следующий запрос:
- WorkItemDetails (вся информация для рабочего элемента)
Запросов обновляются обработчиками, которые обрабатывают событие домена, поднятое WorkItem агрегат (ов) (после того, как они» повторно сохранены в EventStore). Все эти события содержат WorkItemId. Я хотел бы иметь возможность перестраивать запросы «на лету» при необходимости, загружая все соответствующие события и обрабатывая их последовательно. Это связано с тем, что мои пользователи обычно не получают доступ к WorkItems, созданным год назад, поэтому мне не нужно обрабатывать эти запросы. Поэтому, когда я получаю запрос, которого не существует, я мог бы его перестроить и сохранить в хранилище ключей/значений с помощью TTL.
События домена имеют aggregateId (используется как поток потока событий и ключ осколка) и sequenceId (используется как eventId в потоке событий).
Итак, моя первая попытка состояла в создании большого агрегата под названием WorkItem, в котором собрана коллекция участников и коллекция действий. Участники и действия - это объекты, которые живут только внутри WorkItem. Участник ссылается на userId, и действие ссылается на участникId. Они могут иметь больше информации, но это не относится к этому упражнению. С помощью этого решения мой большой агрегат WorkItem может гарантировать, что команды являются идемпотентными, потому что я могу проверить, что я не добавляю повторяющихся участников или действия, и если я хочу перестроить запрос WorkItemDetails, я просто загружаю/обрабатываю все события для данного WorkItemID.
Это работает отлично, потому что, поскольку у меня есть только один агрегат, WorkItemId может быть aggregateId, поэтому, когда я перестраиваю запрос, я просто загружаю все события для данного WorkItemId. Однако это решение имеет проблемы с производительностью большого агрегата (зачем загружать всех участников и действия для обработки команды ChangeTitle?).
Итак, моя следующая попытка состоит в том, чтобы иметь разные агрегаты, все с одним и тем же WorkItemId как свойство, но только агрегат WorkItem имеет его как aggregateId. Это устраняет проблемы с производительностью, я могу обновить запрос, потому что все события содержат WorkItemId, но теперь моя проблема в том, что я не могу его перестроить с нуля, потому что я не знаю агрегированные данные для других агрегатов, поэтому я не могу загрузить их потоки событий и обрабатывать их.У них есть свойство WorkItemId, но это не их реальный aggregateId. Также я не могу гарантировать, что я обрабатываю события последовательно, потому что у каждого агрегата будет свой собственный поток событий, но я не уверен, что это настоящая проблема.
Другим решением, о котором я могу думать, является создание выделенного потока событий для консолидации всех событий WorkItem, создаваемых несколькими агрегатами. Поэтому у меня могут быть обработчики событий, которые просто добавляют события, инициированные Участником, и Действия к потоку событий, идентификатор которого будет похож на «{workItemId}: allevents». Это будет использоваться только для восстановления запроса WorkItemDetails. Это звучит как хак. В основном я создаю «совокупность», которая не имеет бизнес-операций.
Какие еще решения у меня есть? Необычно ли перестраивать запросы «на лету»? Можно ли это сделать, если для создания одного и того же запроса используются события для нескольких агрегатов (несколько потоков событий)? Я искал этот сценарий и не нашел ничего полезного. Я чувствую, что мне не хватает чего-то, что должно быть очень очевидно, но я не понял, что.
Любая помощь по этому поводу очень ценится.
Благодаря
Не могли бы вы более подробно объяснить «Я не могу восстановить его с нуля, потому что я не знаю aggregateIds для других агрегатов, поэтому я не могу загрузить свои потоки событий и обрабатывать их «? Возможно ли содержать actionId в ExcecuteActionCommand? ActionId может быть получен путем запроса с помощью workItemId после добавления действия. Или я что-то пропустил? – Hippoom
Да, восстановление WorkItemДетали с нуля будут загружать все события (из eventstore) для данного WorkItemId. Но события для Участника и Действия хранятся в собственных потоках событий, хотя полезная нагрузка события может содержать WorkItemId.Вот почему я думал о наличии выделенного потока событий для WorkItemDetails, так что при перестройке я просто загружал события в этот поток событий. Я не хочу делать несколько запросов (возможно, сотни) для загрузки событий из нескольких потоков (агрегатов). –
Извините, я до сих пор не понимаю. WorkItemDetails - это совокупность или запрос? Сохраняется ли это как последнее состояние WorkItem, если это запрос? – Hippoom