Как «объединить» два Агрегированных Корня при подготовке View Model? - PullRequest
12 голосов
/ 18 февраля 2011

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

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

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

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

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

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

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

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

Ответы [ 5 ]

3 голосов
/ 27 февраля 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);

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

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, которое будет иметь идентификатор автора.

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

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

3 голосов
/ 20 февраля 2011

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

2 голосов
/ 19 февраля 2011

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

0 голосов
/ 19 февраля 2011

У последнего проблемы с параллелизм. Автор данных может быть не доступно в View Model в то время Событие BookAdded обрабатывается.

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

0 голосов
/ 19 февраля 2011

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

...