Поиск совокупного идентификатора на основе зависимости

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

У меня есть служба, которая определяет некоторые агрегаты, и эти агрегаты публикуют события. Давайте выберем один агрегат из службы и назовем его A. Теперь, я определяю другую службу, у которой есть некоторый агрегат, который должен работать с A, давайте вызовем вторую совокупность B.

Когда A публикует какое-то событие, я хочу отправить команду B (в идеале через сагу), но мне трудно вычислить соответствующий идентификатор B (A не знает, что B существует, поэтому события, которые он публикует, не имеют никакого намека на как рассчитать id)

Я могу придумать несколько возможных сценариев:

Сначала можно было бы статически вычислить идентификатор B на основе идентификатора A, например, в аксоне я мог бы сделать что-то вроде some-id-${suffix}, поэтому, когда я получаю событие от A с some-id, я могу сразу узнать, что оно должно быть отправлено на some-id-B

Второй будет использовать читаемую сторону? (я не уверен, как это правильно называется) и запросить вещь и попытаться найти идентификатор B на основе идентификатора A, но это кажется немного излишним.

Могу ли я прочитать что-нибудь, что могло бы провести меня по возможным сценариям и подсказать, как с ними справиться? Спасибо!


person PolishCivil    schedule 18.04.2019    source источник
comment
Трудно ответить без контекста. Какая связь между A и B именно в контексте вашего приложения? Вы сказали, что A не знает B. Знает ли B A? Может, А и Б объединить?   -  person Dnomyar    schedule 18.04.2019
comment
Да, я понимаю, что без конкретного контекста задавать такие вопросы довольно сложно. Предположим, что контекст - это игры. У вас есть совокупность пользователей, которая в основном поддерживается в другом микросервисе, теперь вы хотите создать игроков, связанных с этим конкретным пользователем. Я могу прекрасно управлять созданием, но я не уверен, как управлять поведением проигрывателя, когда пользовательский микросервис публикует какое-то событие. Например, я хочу отключить проигрыватель, когда пользователь был отключен и т. Д. Я думаю, что это имеет какое-то отношение к отображению контекста? я не уверен   -  person PolishCivil    schedule 18.04.2019
comment
Если у одного пользователя один игрок, вы можете использовать один и тот же идентификатор для обоих. Если на одного пользователя приходится несколько игроков, у вас может быть ссылка на вашего пользователя в совокупности игроков. Таким образом, когда пользователь BC публикует событие об отключении пользователя с помощью идентификатора пользователя, у вас есть необходимая информация в проигрывателе BC, чтобы идентифицировать игрока по идентификатору пользователя, а затем отключить его.   -  person Dnomyar    schedule 18.04.2019
comment
Хм, это интересно, поэтому у меня могут быть одинаковые идентификаторы для разных типов агрегатов, что решает отношения один ко многим. Хм, как тогда будет выглядеть процесс идентификации для отношений «один ко многим»? пользователь публикует отключенное событие для идентификатора xxx, и с этим идентификатором связано 5 игроков? В этом случае я бы относился к игроку как к сущности? Значит, у меня будет агрегирование пользователя с объектами игрока в моем игроке BC?   -  person PolishCivil    schedule 18.04.2019
comment
Что ж, я предлагал что-то вроде этого: 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.2019
comment
Возможно, стоит подумать, нужны ли вам два отдельных BC для player и user   -  person Dnomyar    schedule 18.04.2019
comment
Что ж, да, для отношений один-ко-многим, в которые попадают правила DDD (я не могу охватить согласованность между несколькими агрегатами, поэтому для его решения потребуется иметь Entites) По крайней мере, теперь я уверен, что сопоставление одного идентификатора с другим - это нормально, Спасибо за это.   -  person PolishCivil    schedule 18.04.2019


Ответы (1)


Насколько я понимаю, у вас есть связь от агрегата B к агрегату A. Такие отношения нормальны и возникают постоянно.

Примечание. Поскольку этот вопрос носит очень общий характер и не имеет контекста, я могу что-то упустить. Если есть более частный случай, чем описанный, сообщите мне об этом.

Это отличное чтение для агрегированного дизайна

Примечание. Прежде чем читать оставшуюся часть ответа, посмотрите это видео от Мартина Фаулера. , Я настоятельно рекомендую его, поскольку он очень подробно объясняет концепции, связанные с событиями и командами.

Примечание. Поскольку термин "объект" также очень важен, я больше не буду использовать агрегат, поэтому предположим, что каждый объект (Игрок, Пользователь, Game) являются корнем своего собственного агрегата и являются границей согласованности, поэтому в этом случае будет использоваться согласованность в конечном итоге по событиям домена. Я также буду игнорировать CQRS в данный момент, чтобы не говорить о стороне чтения и стороне записи. Мы обсудим и реализуем Модель

Возьмем пример с игрой. У вас есть Игрок, который должен представлять Пользователя в Игре. Сущность Player должна каким-либо образом ссылаться на игру и пользователя. Это может быть по прямой ссылке или по ID. В случае распределенной системы это будет по ID. В нашем примере. Давайте использовать UUID (например, 8d670541-aa51-470b-9e72-1d9227669d0c) для идентификаторов, чтобы мы могли генерировать их случайным образом без необходимости определять схему, автоматически генерировать порядковый номер (как в базах данных SQL ) или специальный алгоритм. Допустим, у пользователя есть статистика пользователя. Поэтому, когда Игрок набирает очки (например, убивая других игроков в стрелялке), должна быть создана и обновлена ​​сущность UserStatistics, если она не существует. Статистика пользователя также должна ссылаться на пользователя по идентификатору, поэтому мы имеем зависимость от статистики пользователя к пользователю.

Статистика пользователя будет выглядеть так:

UserStatistics {
  UUID UserID,
  uint KillsCount,
  uint GamesPlayedCount
}

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

Когда Пользователь присоединяется к Игре, создается объект Игрок с userID и 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 может подписаться на события и обновлять статистику.

Когда Пользователь присоединяется к Игре, начинается процесс сбора статистики, и мы обновляем (или создаем, если она не существует) его Статистику пользователя Сколько игр он сыграл. В то же время, когда Игрок совершает убийство, нам придется снова обновить статистику.

StartGatheringUserStatisticsCommand будет запускаться из события UserJoinedGameEvent.

Давайте добавим событие PlayerMadeKillEvent, которое выглядит следующим образом:

PlayerMadeKillEvent {
 UUID UserID,
 UUID PlayerID,
 UUID GameID
}

UserStatisticsService подпишется на PlayerMadeKillEvents и обновит UserStatistics, используя PlayerMadeKillEvent.UserID, чтобы найти статистику для конкретных <сильных > Пользователь.

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

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

person expandable    schedule 18.04.2019
comment
Это отличный пример отображения контекста. Спасибо! Итак, в основном вы говорите, что я должен `` передавать '' эти ссылки через события, вместо того, чтобы связывать их статически? (Бывший игрок имеет уникальный идентификатор, но когда события публикуются, и наблюдателям этих событий требуется идентификатор пользователя этого игрока, я должен передать его через событие) - person PolishCivil; 18.04.2019
comment
Добро пожаловать, рад, что вам понравилось :) Да, именно так. Добавляйте данные к событиям и используйте их для уведомлений о том, что произошло. Эти данные могут быть использованы подписчиками. Это создает слабую связь, и вы можете легко добавлять новые агрегаты и службы, подписавшись на события. В видео Мартин Фаулер называет этот паттерн "перенос состояния по событию". Вы можете добавить столько данных, сколько вам нужно, чтобы вам не приходилось делать дополнительные вызовы издателю события (будьте осторожны, у вас не должно быть события со 100 свойствами :) Если что-то подобное произойдет, подумайте о том, чтобы разбить их к нескольким событиям) - person expandable; 18.04.2019
comment
В другой ссылке Вон Вернон описывает метод «Ссылка по идентификатору» и то, как он позволяет объединять более нечеткие агрегаты. Вы также можете искать дополнительную информацию о ссылках на агрегаты из нескольких ограниченных контекстов. Этот документ adrianmarriott.net/logosroot/papers/LifeBeyondTxns.pdf от Path Helland также отличное чтение. Это не о DDD, но имеет большое сходство с ним, определяя сущности и действия между ними: - person expandable; 18.04.2019
comment
Ты мой спаситель ‹3 - person PolishCivil; 18.04.2019
comment
Хм, просто вопрос о вещах доставки событий, я должен рассмотреть хотя бы одну доставку, что означает, что событие может быть обработано дважды, при условии, что необходимо будет вычислить новый идентификатор игрока на основе того, что находится в полученном событии (например, идентификатор пользователя )? так что создание нового плеера идемпотентно? - person PolishCivil; 18.04.2019
comment
А да, доставка мероприятия :) Вот это интересно. Вам может понадобиться идемпотентность. Конкретный случай повлияет на решение. Здесь играет роль уникальность. Игрок должен быть уникальным для пользователя в конкретной игре. Пользователь может присоединиться к нескольким Играм. Вы можете случайным образом сгенерировать идентификатор игрока, используя UUID, но у вас есть логика, которая проверяет, не существует ли игрок с этим идентификатором пользователя и GameID. Вы также можете отказаться от использования UUID и вместо этого использовать комбинацию UserID и GameID в качестве PlayerID. Игрок уничтожается после того, как пользователь уходит, поэтому наличие UUID может не принести никакой пользы. - person expandable; 18.04.2019