Как соединить два агрегированных корня при подготовке модели представления?

Предположим, что Book и Author являются агрегированными корнями в моей модели.

В модели чтения у меня есть таблица AuthorsAndBooks, которая представляет собой список авторов и книг, к которым присоединился Book.AuthorId

Когда запускается событие BookAdded, я хочу получить данные Author для создания новой строки AuthorsAndBooks.

Поскольку Book является агрегированным корнем, информация о Author не включается в событие BookAdded. И я не могу включить его, потому что Author root не имеет геттеров (согласно руководящим принципам всех примеров и сообщений о CQRS и Event Sourcing).

Обычно на этот вопрос я получаю два типа ответов:

  1. Обогатите событие своего домена всеми данными, которые вам нужны в обработчиках событий. Но, как я уже сказал, я не могу сделать это для агрегированных корней.
  2. Используйте доступные данные из View Model. Т.е. загрузите Author из модели представления и используйте его для построения строки AuthorsAndBooks.

У последнего есть проблемы с параллелизмом. Данные об авторах могут быть недоступны в модели просмотра во время обработки события BookAdded.

Какой подход вы используете для решения этой проблемы? Спасибо.


person Dmitry Schetnikovich    schedule 18.02.2011    source источник
comment
Просто любопытно. Я всегда думал, что в корне не должно быть сеттера, поскольку все настройки выполняются через события? Однако у рута определенно могут быть геттеры ИМХО. Пожалуйста, поправьте меня, если я ошибаюсь ...   -  person cs0815    schedule 03.06.2014


Ответы (5)


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

person Yves Reynhout    schedule 20.02.2011
comment
Хорошо, я вижу, что моя проблема возникла сама собой, и я согласен, что я должен быть настоящим. Я вижу, что обработчики событий (идемпотентные и упорядоченные) работают на вас. Это меня вдохновляет! :) Но ... Послушайте, я могу правильно построить все AR в модели чтения (во многих потоках и в правильном порядке (просто переставив очередь, как вы упомянули)), но тогда мне нужно создать какие-то отчеты, которые охватывают много AR (несмотря на правильность дизайна, нетривиальная модель все равно будет иметь как минимум 2 AR). Может, стоит побороться за создание таких отчетов из потока событий, а может это еще одна граница согласованности? - person Dmitry Schetnikovich; 21.02.2011
comment
Что, если бы обработчики событий строили бы только простые снимки AR, а созданные снимки использовались бы другой границей согласованности, которая выполняет все сложные вычисления и соединение между снимками для создания другой модели внутри этой границы? Я все еще пытаюсь понять, как далеко мы можем зайти, обрабатывая события, чтобы создать нашу модель чтения? Где конец этого процесса и когда мы должны остановить его и начать использовать доступную модель (созданную обработчиками событий), чтобы преобразовать ее в другую (используя Map-Reduce или SQL)? Я вижу, что просто не могу подготовить все виды необходимых мне представлений, используя только обработчики событий ... - person Dmitry Schetnikovich; 21.02.2011
comment
Извините за такой длинный комментарий. Ничего страшного, если ты их все пропустишь :) - person Dmitry Schetnikovich; 21.02.2011
comment
В зависимости от сложности вы можете выполнить (2) или использовать подход, похожий на сагу, когда вы ждете обоих событий и преобразуете их в новое событие, которое, в свою очередь, может быть обработано. - person Yves Reynhout; 26.02.2011
comment
На самом деле, это вопрос выбора. Попытка - лучшая школа. - person Yves Reynhout; 26.02.2011

В вашем вопросе отсутствует какой-то контекст, например, какой пользовательский сценарий приводит к этому событию и из какого состояния вы начинаете? Если бы вы писали тесты BDD для этого случая, как бы они выглядели? Знание этого очень поможет ответить на ваш вопрос.

То, как вы решаете проблему соотнесения книги с автором, зависит от предметной области. Сначала мы предполагаем, что для вашего домена имеет смысл иметь агрегат для автора и агрегат для книги, например, если бы я писал библиотечную систему, я сомневаюсь, что у меня был бы агрегат для авторов, поскольку меня не волнует автор без своей книги, меня волнуют книги.

Что касается отсутствия геттеров, стоит упомянуть, что у агрегированных корней нет геттеров из-за предпочтения скажи-не-спрашивай стиль ООП. Однако вы можете сказать одному AR сделать что-то, а затем, если вам нужно, что-то скажет другому AR. Отчасти важно то, что AR сообщает другим о себе, а не пишет код, в котором вы его спрашиваете, а затем передаете.

Наконец, я должен спросить, почему у вас нет идентификатора автора, когда вы добавляете книгу? Как тогда вообще узнать, кто автор? Я предполагаю, что вы могли бы просто сделать следующее (мой код предполагает, что вы используете свободный интерфейс для создания AR, но вы можете заменять фабрики, конструкторы, все, что вы используете):

CreateNew.Book()
  .ForAuthor(command.AuthorId)
  .WithContent(command.Content);

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

var author = CreateNew.Author()
  .WithName(command.AuthorName);

var book = CreateNew.Book()
  .ForAuthor(author.Id)
  .WithContent(command.Content);

Возможно, проблема в том, что у вас нет получателя для совокупного корневого идентификатора, который я не считаю необходимым или обычным. Однако, если для вас важна инкапсуляция идентификатора или ваше событие BookAdded требует больше информации об авторе, чем может предоставить идентификатор, тогда вы можете сделать что-то вроде этого:

var author = CreateNew.Author()
  .WithName(command.AuthorName);
var book = author.AddBook(command.Content);

// Adds a new book belonging to this Author
public Book AddBook(BookContent content) {
  var book = CreateNew.Book()
    .ForAuthor(this.Id)
    .WithContent(command.Content);
}

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

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

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

person Chris Nicola    schedule 27.02.2011

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

person Rinat Abdullin    schedule 19.02.2011
comment
Правильно ли я, что вы остаетесь для пункта № 2 в сочетании с упорядочиванием и дедупликацией событий? Итак, обработчик события BookAdded должен запрашивать модель чтения, чтобы получить информацию об авторе? - person Dmitry Schetnikovich; 20.02.2011

Первое, что я хотел бы спросить, это почему в модели чтения возникают проблемы с параллелизмом. Если клиент отправляет ссылку на агрегат автора внутри команды AddBook, откуда он получил информацию? Если книга и автор создаются одновременно, то ваше мероприятие, вероятно, можно обогатить. Сообщите мне, если я что-то упустил.

person Mafuba    schedule 18.02.2011
comment
События AuthorAdded и BookAdded могут быть очень близки друг к другу в потоке событий, и возможно, что эти события будут обрабатываться одновременно обработчиками событий (в случае, если мы используем несколько потоков для обработчиков событий). Таким образом, обработчик BookAdded может быть вызван перед обработчиком AuthorAdded, а обработчик BookAdded не найдет информацию об авторе в модели чтения. Спасибо за ваш ответ! - person Dmitry Schetnikovich; 19.02.2011
comment
Если вы имеете дело с командами, которые имеют данные из нескольких агрегатов в одно и то же время, и эти команды не имеют данных из модели чтения, И в вашем домене такая сложность, что вы не можете упорядочить события, единственный другой подход, который я могу придумать было бы использовать сагу. Храните дополнения к книгам как длительные процессы, которые ждут, пока события автора не будут обработаны нужным вам способом. Без дополнительной информации кажется, что ваша книга и домен автора проще, чем вам нужно. Спросите своих экспертов, насколько вероятна ситуация, которую вы пытаетесь предотвратить. - person Mafuba; 21.02.2011

У последнего есть проблемы с параллелизмом. Данные об авторе могут быть недоступны в модели представления во время обработки события BookAdded.

А как насчет «обработки события позже»? Таким образом, вы просто помещаете его в конец очереди, пока эти данные не будут доступны (возможно, с ограничением в x попыток и x времени между каждой попыткой).

person Maxem    schedule 19.02.2011
comment
Это хорошее возможное решение. Может быть, я просто усложню свой проект введением такого решения ... Но все равно спасибо за подсказку! - person Dmitry Schetnikovich; 21.02.2011