Насколько я понимаю, у вас есть связь от агрегата B к агрегату A. Такие отношения нормальны и возникают постоянно.
Примечание. Поскольку этот вопрос носит очень общий характер и не имеет контекста, я могу что-то упустить. Если есть более частный случай, чем описанный, сообщите мне об этом.
Это отличное чтение для агрегированного дизайна
Примечание. Прежде чем читать оставшуюся часть ответа, посмотрите это видео от Мартина Фаулера. , Я настоятельно рекомендую его, поскольку он очень подробно объясняет концепции, связанные с событиями и командами.
Примечание. Поскольку термин "объект" также очень важен, я больше не буду использовать агрегат, поэтому предположим, что каждый объект (Игрок, Пользователь, Game) являются корнем своего собственного агрегата и являются границей согласованности, поэтому в этом случае будет использоваться согласованность в конечном итоге по событиям домена. Я также буду игнорировать CQRS в данный момент, чтобы не говорить о стороне чтения и стороне записи. Мы обсудим и реализуем Модель
Возьмем пример с игрой. У вас есть Игрок, который должен представлять Пользователя в Игре. Сущность Player должна каким-либо образом ссылаться на игру и пользователя. Это может быть по прямой ссылке или по ID. В случае распределенной системы это будет по ID. В нашем примере. Давайте использовать UUID (например, 8d670541-aa51-470b-9e72-1d9227669d0c) для идентификаторов, чтобы мы могли генерировать их случайным образом без необходимости определять схему, автоматически генерировать порядковый номер (как в базах данных SQL ) или специальный алгоритм. Допустим, у пользователя есть статистика пользователя. Поэтому, когда Игрок набирает очки (например, убивая других игроков в стрелялке), должна быть создана и обновлена сущность UserStatistics, если она не существует. Статистика пользователя также должна ссылаться на пользователя по идентификатору, поэтому мы имеем зависимость от статистики пользователя к пользователю.
Статистика пользователя будет выглядеть так:
UserStatistics {
UUID UserID,
uint KillsCount,
uint GamesPlayedCount
}
Поскольку проигрыватель не может существовать без пользователя, сначала необходимо создать пользователя. Поскольку Игрок является частью Игры, это означает, что сначала должна быть создана Игра. Давайте определимся с терминологией в нашем Универсальном языке. Пользователь присоединяется к Игре, становясь в ней Игроком. Предположим, что Игра будет создана кем-то другим, а не Пользователями, чтобы избежать обсуждения ситуации, когда Пользователь создает игру и должен присоединиться к ней одновременно и т. Д. Если это произойдет в той же транзакции и т. Д. Игра будет чем-то вроде MMO, где она создается кем-то, и к ней могут присоединиться обычные пользователи.
Когда Пользователь присоединяется к Игре, создается объект Игрок с userID em> и gameID. Создание Player без userID и gameID недопустимо.
Давайте обсудим проблему с командами и событиями. Команды могут быть инициированы по событиям. Позвольте использовать шаблон наблюдателя. Одна сущность должна будет наблюдать другую сущность на предмет событий. В нашем примере это означает, что существует зависимость от UserStatistics (наблюдатель) до User и Player (создателя темы / сообщения). Тот факт, что определенная команда в UserStatistics будет выполняться как реакция на событие, инициированное Player и Пользователь не должен каким-либо образом влиять на Player или Player. Использование события для преднамеренного запуска специальной команды в пассивно-агрессивном стиле - не очень хорошая стратегия. Команды могут быть вызваны событием, но не только одна конкретная команда может быть инициирована. Может быть запущено множество различных команд, и только зависимые объекты, службы или системы должны заботиться о том, что происходит. Проигрыватель и Пользователь просто предоставляют События.
Когда Пользователь присоединяется к Игре и создается Игрок, он будет ссылаться на оба объекта по идентификатору, поэтому он будет выглядеть примерно так:
Player {
UUID GameID,
UUID UserID
}
Также событие UserJoinedGameEvent будет вызвано из объекта User (оно может быть вызвано из Game, но мы выберем User). . Это выглядит так:
UserJoinedGameEvent {
UUID GameID,
UUID UserID,
UUID PlayerID
}
UserStatisticsService может подписаться на события и обновлять статистику.
Когда Пользователь присоединяется к Игре, начинается процесс сбора статистики, и мы обновляем (или создаем, если она не существует) его Статистику пользователя strong > Сколько игр он сыграл. В то же время, когда Игрок совершает убийство, нам придется снова обновить статистику.
StartGatheringUserStatisticsCommand будет запускаться из события UserJoinedGameEvent.
Давайте добавим событие PlayerMadeKillEvent, которое выглядит следующим образом:
PlayerMadeKillEvent {
UUID UserID,
UUID PlayerID,
UUID GameID
}
UserStatisticsService подпишется на PlayerMadeKillEvents и обновит UserStatistics, используя PlayerMadeKillEvent.UserID, чтобы найти статистику для конкретных <сильных > Пользователь.
Когда Пользователь выходит из Игры, может возникнуть UserQuitsGameEvent и сбор статистики может прекратиться.
В этом примере у нас не было конкретной схемы для генерации специальных идентификаторов, мы можем ссылаться на другие агрегаты, которые будут созданы первыми, а затем использовать их идентификаторы.
person
expandable
schedule
18.04.2019
user.User(id: user.UserId)
вuser
BC иplayer.Player(id: player.PlayerId, userId: player.UserId)
вplayer
BC. В этом случае вам понадобится контекстное сопоставление междуuser.UserId
иplayer.UserId
. Но ваша идея иметь отношение 1 к 1 междуuser.User
иplayer.User
, гдеplayer.User
будет агрегатом, содержащим несколькоplayer.Player
, интересна. Я считаю, что для ответа на этот вопрос вам нужно думать о жизненном цикле междуplayer.User
иplayer.Player
(создание, удаление и т. Д.). - person Dnomyar   schedule 18.04.2019player
иuser
- person Dnomyar   schedule 18.04.2019