2014-12-05 2 views
3

Я использую подход 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. Это звучит как хак. В основном я создаю «совокупность», которая не имеет бизнес-операций.

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

Любая помощь по этому поводу очень ценится.

Благодаря

+0

Не могли бы вы более подробно объяснить «Я не могу восстановить его с нуля, потому что я не знаю aggregateIds для других агрегатов, поэтому я не могу загрузить свои потоки событий и обрабатывать их «? Возможно ли содержать actionId в ExcecuteActionCommand? ActionId может быть получен путем запроса с помощью workItemId после добавления действия. Или я что-то пропустил? – Hippoom

+0

Да, восстановление WorkItemДетали с нуля будут загружать все события (из eventstore) для данного WorkItemId. Но события для Участника и Действия хранятся в собственных потоках событий, хотя полезная нагрузка события может содержать WorkItemId.Вот почему я думал о наличии выделенного потока событий для WorkItemDetails, так что при перестройке я просто загружал события в этот поток событий. Я не хочу делать несколько запросов (возможно, сотни) для загрузки событий из нескольких потоков (агрегатов). –

+0

Извините, я до сих пор не понимаю. WorkItemDetails - это совокупность или запрос? Сохраняется ли это как последнее состояние WorkItem, если это запрос? – Hippoom

ответ

1

Я не думаю, что вы должны разработать свои агрегаты с запрашивая проблемами в виду. Сторона «Чтение» предназначена для этого.

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

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

Если вы идете несколько малых агрегаты маршрута и ваш Read модель нуждается в информации из нескольких источников, чтобы обновить себя, у вас есть несколько вариантов:

  • Пополните излучаемое событие с дополнительными данными

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

См CQRS events do not contain details needed for updating read model

+0

Я знаю, что здесь нет никакой магии, но, похоже, неправильно создавать агрегаты, чтобы я мог дешифровать перестроить запросы, но если я не могу, в любой момент времени иметь последовательное представление о моих данных, как я могу скажите, что мои агрегаты хорошо разработаны для целей согласованности? Возможно, мне не хватает расширенных функций в EventStore, которые являются потоками событий, которые являются проекциями из других потоков событий, и не имеют никакого отношения к тому, как создаются агрегаты. Я пытаюсь сделать что-то, что не так распространено в CQRS? –

+0

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

+0

Я позволю другим ответить на это, но да, исходя из моего опыта, то, что вы пытаетесь сделать, редко встречается в CQRS. – guillaume31