Из того, что я понимаю, у вас есть отношение от агрегата B к агрегату A. Такие отношения нормальны и они возникают постоянно.
Примечание. Поскольку этот вопрос носит весьма общий характер и не имеет контекста, я могу что-то упустить.Если есть более особый случай, чем описанный, сообщите мне об этом.
Это отличное прочтение для совокупного дизайна
Примечание:Проверьте это видео от Мартина Фаулера, прежде чем читать оставшуюся часть этого ответа, я настоятельно рекомендую его, поскольку он очень подробно объясняет понятия, связанные с событиями и командами.
Примечание: Поскольку термин «сущность» также очень важен, с этого момента я больше не буду использовать агрегат, поэтому предположим, что каждая сущность ( Игрок , Пользователь , Игра )являются корнем их собственной совокупности и являются границей согласованности, поэтому будет использоваться возможная согласованность в этом случае с событиями домена.Я также буду игнорировать CQRS в данный момент, чтобы избежать необходимости говорить о стороне чтения и стороне записи.Мы обсудим и внедрим Модель
Давайте рассмотрим пример с игрой.У вас есть Player , который должен представлять User в Game .Сущность Player должна каким-либо образом ссылаться на Game и User .Это может быть по прямой ссылке или по идентификатору.В случае распределенной системы это будет по ID.В нашем примере давайте используем идентификаторы UUID (например, 8d670541-aa51-470b-9e72-1d9227669d0c ) для идентификаторов, поэтому мы можем генерировать их случайным образом без определения схемы, автоматически генерировать порядковый номер (как в базах данных SQL)или специальный алгоритм.Предположим, что Пользователь имеет UserStatistics .Таким образом, когда Player набирает очки (например, убивая других игроков в стрелялке), сущность UserStatistics должна быть создана, если она не существует и не обновлена. UserStatistics должен также ссылаться на User по идентификатору, поэтому у нас есть зависимость от UserStatistics до User .
UserStatistics будет выглядеть следующим образом:
UserStatistics {
UUID UserID,
uint KillsCount,
uint GamesPlayedCount
}
Поскольку Player не может существовать без User , User долженбыть создан первым.Поскольку Player является частью Game , это означает, что Game следует создать в первую очередь.Давайте определим некоторую терминологию в нашем вездесущем языке .A Пользователь 'присоединяется' a Игра , становясь Игроком в ней.Предположим, что игра будет создана кем-то другим, а не Пользователи , чтобы избежать необходимости обсуждать ситуацию, когда Пользователь создает игру и должен присоединиться к ней в то же время и т. Д.это происходит в той же транзакции и т. д. ... Игра будет чем-то похожим на MMO, где она создается кем-то, и обычные пользователи могут присоединиться.
Когда Пользователь присоединяется к a Game , тогда сущность Player будет создана с userID и gameID .Создание Player без userID и gameID недопустимо.
Давайте обсудим проблему с Команды и События . Команды могут быть Инициированы на События . Давайте использовать Наблюдательский паттерн . Одна сущность должна будет наблюдать за другой сущностью для событий. В нашем примере это означает, что зависимость от UserStatistics (наблюдатель) до Пользователь и Игрок (субъект / производитель сообщений). Тот факт, что конкретная Команда на UserStatistics будет выполнена как реакция на Событие , поднятое с Игрок и Пользователь никоим образом не должен влиять на игрока или игрока . Использование Event для преднамеренного запуска специальной Command в пассивном агрессивном стиле - не очень хорошая стратегия. Команды могут быть вызваны Событием , но не только одна конкретная Команда может быть Триггером . Много различных команд могут быть запущены, и только зависимые сущности , Службы или Системы должны заботиться о том, что происходит. Игрок и Пользователь просто предоставляют События .
Когда Пользователь присоединяется к Game и Player создается, он будет ссылаться на обе сущности по ID, поэтому он будет выглядеть примерно так:
Player {
UUID GameID,
UUID UserID
}
Также UserJoinedGameEvent событие будет инициировано из Пользователь сущности (оно может быть поднято из Игра , но мы выберем Пользователь ). Это выглядит так:
UserJoinedGameEvent {
UUID GameID,
UUID UserID,
UUID PlayerID
}
UserStatisticsService может подписаться на события и обновлять статистику.
Когда Пользователь присоединяется к Игре , начнется процесс сбора статистики, и мы обновим (или создадим, если она не существует) его UserStatistics Сколько игр он сыграл. В то же время, когда Player совершает убийство, нам придется снова обновлять статистику.
StartGatheringUserStatisticsCommand будет запускаться из UserJoinedGameEvent события.
Давайте добавим событие PlayerMadeKillEvent , которое выглядит следующим образом:
PlayerMadeKillEvent {
UUID UserID,
UUID PlayerID,
UUID GameID
}
UserStatisticsService подпишется на PlayerMadeKillEvents и обновит UserStatistics , используя PlayerMadeKillEvent.UserID , чтобы найти статистику для конкретного Пользователь .
Когда Пользователь выходит из Игры , UserQuitsGameEvent может быть поднят и сбор статистики может быть остановлен.
В этом примере у нас не было конкретной схемы для генерации специальных идентификаторов, мы можем ссылаться на другие агрегаты, которые будут созданы сначала, а затем использовать их идентификатор.