Подтвердить что ты не робот

Как "присоединиться" к двум совокупным корням при подготовке View Model?

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

Как вы решаете проблему связывания книги с автором, зависит от домена. Во-первых, мы предполагаем, что для вашего домена имеет смысл иметь совокупность для Автора и совокупность для Книги, например, если я писал библиотечную систему, я сомневаюсь, что у меня будет свод для авторов, поскольку меня не волнует автор без его/ее книги, я забочусь о книгах.

Что касается отсутствия геттеров, стоит упомянуть, что у совокупных корней нет геттеров из-за предпочтения tell-don't-ask стиль ООП. Однако вы можете сказать одному 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 требуется больше информации об авторе, чем может указать 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);
}

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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