Вызов функции кросс-агрегированного расчета для обновления модели чтения после применения команды

Я новичок в CQRS и мне нужен совет по следующей ситуации в моем дизайне. Команда обновляет состояние агрегата A; следовательно, модель чтения должна быть обновлена ​​с использованием метода расчета перекрестных агрегатов; этот метод принадлежит другому агрегату B, который содержит ссылку на агрегат A; метод является функцией состояний агрегата B и указанного агрегата A. Где правильное место для вызова этой функции?

Мои соображения (можно пропустить):

  • Обработчик команд, обновляющий состояние агрегата A, может технически извлечь агрегат B из репозитория, вызвать вычисление для него и поместить результат в событие домена; однако я считаю, что извлечение агрегатов, кроме изменяемого, не является задачей обработчика команд, даже для чтения; Кроме того, это не задача обработчика команд выполнять вычисления только для отправки с событиями, а не для изменения состояния домена.
  • Событие домена ('Aggregate A updated'), вызванное агрегатом A, содержит только обновленное состояние агрегата A, недостаточно информации о состоянии агрегата B. Обработчик событий модели чтения не имеет доступа к модели предметной области , поэтому он не может ни получить агрегат B, ни вызвать нужную функцию на агрегате B для обновления модели чтения.
  • Я знаю, что любое состояние, необходимое команде, которое является внешним по отношению к изменяемому агрегату, должно передаваться вместе с командой. Таким образом, служба приложения перед отправкой команды может получить состояние агрегата B (из модели чтения) и поместить его в команду. Для этого мне пришлось бы переместить функцию из агрегата B в какую-либо службу и передать туда состояния как A, так и B. Это сделало бы агрегат B более анемичным. Плюс вышеупомянутая проблема с выполнением вычислений в обработчике команд.
  • Я читал людей, предполагающих, что любые вычисления, которые интересуют только модели чтения, принадлежат самой модели чтения. Таким образом, обработчик модели чтения моего события будет просто иметь в своем распоряжении все необходимое состояние и поведение для выполнения вычислений. Однако это означало бы, что мне придется дублировать большую часть концепций модели предметной области на стороне запроса; Было бы слишком сложно иметь полноценную модель чтения.

Я только что подумал о следующем решении: создать в домене обработчик события домена «Aggregate A updated». Он получит агрегат B, вызовет для него метод вычисления, а затем вызовет событие «Результат функции агрегирования B изменен» с новым результатом вычисления в нем. Затем модель чтения сможет получить результат этого события и обновить себя. Это было бы нормально?

На всякий случай обратите внимание, что я не использую Event Sourcing.

Будем очень признательны за любые мысли по поводу этой ситуации. Спасибо!

ОБНОВЛЕНИЕ: конкретизация ситуации

Мои агрегаты - это Workers (агрегат B) и Groups рабочих (агрегат B). Рабочие и группы - это отношения «многие ко многим». Представьте, что и у группы, и у рабочего есть какое-то свойство Value. Рабочий calculateValue() является функцией Ценности рабочего плюс Ценности всех групп, в которых он участвует. Команда, описанная выше, изменяет Value для некоторой группы. В результате все рабочие, участвующие в группе, вернут разные результаты calculateValue().

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


person Pavel S.    schedule 13.09.2017    source источник
comment
Как можно не нуждаться в группе при чтении? В пользовательском интерфейсе, без стороны чтения, как узнать, какие группы доступны, например добавить воркера? И как вы можете обновить «Значение» для группы, если вы не можете запросить группу, чтобы показать ее пользователю?   -  person TomW    schedule 13.09.2017
comment
Также помните, что агрегаты не могут ссылаться на другие агрегаты, а только на их идентификаторы. В любом случае с CQRS ссылка на агрегированный объект будет бесполезна в любом случае, поскольку у него нет методов чтения (агрегаты - это объекты только для записи).   -  person TomW    schedule 13.09.2017
comment
@TomW Модель чтения, о которой я говорю, не должна использоваться пользовательским интерфейсом, который редактирует отношения рабочих и групп. Насколько я понимаю, у вас может быть много моделей для чтения, не так ли? Эта модель скорее предназначена для использования в качестве клиентского компонента, которому нужны только рабочие и рассчитанные на них значения.   -  person Pavel S.    schedule 13.09.2017
comment
@TomW Да, я ссылаюсь на агрегаты по естественным идентификаторам. Я мог бы использовать внедренное репо для получения ссылочного агрегата. Так вы говорите, что я не могу использовать ссылку на другой агрегат в какой-то логике? Разрешен только getAnotherAggregateID() метод?   -  person Pavel S.    schedule 13.09.2017
comment
Да, можно использовать несколько моделей чтения. Хотя лучше всего разделить модели чтения, для одной модели чтения допустимо запрашивать другую, когда она применяет событие, если эта другая модель чтения уже имеет необходимые данные - это компромисс между повторением и разделением, но также может вызывать проблемы с упорядочением (если вам нужно дождаться, пока другая модель чтения догонит - вам может потребоваться опрос). Имеет ли существующая модель чтения группы рассчитанное значение, которое вам нужно? Было бы проще добавить туда?   -  person TomW    schedule 13.09.2017
comment
Я мог бы использовать внедренное репо для получения ссылочного агрегата - вы не можете запрашивать агрегат, вы получаете его бесплатно.   -  person Constantin Galbenu    schedule 13.09.2017
comment
@TomW Спасибо за ваши замечания. У меня появился импульс пересмотреть всю модель и лучше понять принципы CQRS.   -  person Pavel S.    schedule 13.09.2017


Ответы (1)


Обработчик команд, обновляющий состояние агрегата A, может технически извлечь агрегат B из репозитория, вызвать вычисление для него и поместить результат в событие домена; однако я считаю, что извлечение агрегатов, кроме изменяемого, не является задачей обработчика команд, даже для чтения; Кроме того, это не задача обработчика команд выполнять вычисления только для отправки с событиями, а не для изменения состояния домена.

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

Я знаю, что любое состояние, необходимое команде, которое является внешним по отношению к изменяемому агрегату, должно передаваться вместе с командой. Таким образом, служба приложения перед отправкой команды может получить состояние агрегата B (из модели чтения) и поместить его в команду. Для этого мне пришлось бы переместить функцию из агрегата B в какую-либо службу и передать туда состояния как A, так и B. Это сделало бы агрегат B более анемичным. Плюс вышеупомянутая проблема с выполнением вычислений в обработчике команд.

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

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

Это путь. Однако что вы все равно дублируете? Используется ли результат этого вычисления Агрегатом для принятия или отклонения какой-либо из его команд?

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

Если нет, то расчет не должен оставаться внутри агрегата, а только в модели чтения.

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

person Constantin Galbenu    schedule 13.09.2017
comment
Спасибо за ваш ответ! Ответ на ваш вопрос «Используется ли результат этого вычисления агрегатом для принятия или отклонения какой-либо из его команд?» - «нет». Я обновил свой вопрос, в котором я расширил свой случай, указав конкретные агрегаты и отношения между ними. Я по-прежнему считаю, что было бы слишком сложно пойти тем путем, который вы рекомендуете, хотя я считаю, что это концептуально правильно. Жду вашего мнения еще раз. - person Pavel S.; 13.09.2017
comment
@PavelS ваш вариант использования по-прежнему общий, вам нужно быть более конкретным. На самом деле нам нужен точный расчет и то, что вы с ним делаете и где (читать, писать). - person Constantin Galbenu; 13.09.2017
comment
Теперь, я думаю, у меня произошел некоторый сдвиг в моем понимании после @TomW и ваших замечаний о том, что агрегат не подходит для запросов. Я, наверное, надеялся на какие-то нерешительные решения, но теперь вижу, что CQRS - вещь бескомпромиссная :) Придется пересмотреть весь дизайн. Еще раз спасибо. - person Pavel S.; 13.09.2017