Обеспечить строгую согласованность, охватывающую несколько агрегатов

Рассмотрим следующие бизнес-требования:

У нас есть игроки, которые могут играть в игры. Игрок может играть только в одну игру одновременно. Для игры нужны два игрока.

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

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

Как нам нужно определить агрегаты и их границы для обеспечения соблюдения этих бизнес-правил?

Я задумал два подхода:

1. Событийное рукопожатие

заполнитель Player, совокупность Game,

Когда игра запрашивается, она выдвигает GameRequested -событие. Player s подписаться на это событие и ответить соответствующим событием, либо GamePlayerAccepted или же GamePlayerRejected, Только если оба Player s приняли, Game начинается (GameStarted).

Плюсы:

  • Совокупность Player отвечает за управление собственной доступностью, которая соответствует модели домена

Минусы:

  • Ответственность за запуск Game разбросано по нескольким агрегатам (это выглядит как "поддельная"-eventual-консистенция)
  • Много коммуникационных накладных расходов
  • Необходимы меры согласованности, например, освобождение Player Если что-то пошло не так

2. Коллекция-агрегат

заполнитель Player, совокупность GamesManager (с коллекцией объектов-значений ActiveGamePlayers), совокупность Game,

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

Плюсы:

  • Нет событий, обеспечивающих согласованность, таких как GamePlayerAccepted, GamePlayerRejected и так далее

Минусы:

  • Модель предметной области кажется неясной
  • Ответственность Player управлять доступностью сдвинули
  • Мы должны обеспечить, чтобы только один экземпляр GameManager создается и внедряет доменные механизмы, которые позволяют клиенту не беспокоиться о промежуточном агрегате
  • независимый Game -старты мешают друг другу, потому что GameManager Агрегат блокирует себя
  • Необходимость оптимизации производительности, так как GameManager -агрегат собирает всех активных игроков игры, которые будут десятки миллионов

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

1 ответ

Я бы пошел с рукопожатием на основе событий, и вот как я бы реализовать:

Из того, что я понимаю, вам понадобится Game процесс реализован как Saga, Вы также должны будете определить Player совокупность, а RequestGame команда, а GameRequested событие, а GameAccepted событие, а GameRejected событие, а MarkGameAsAccepted команда, а MarkGameAsRejected команда, а GameStarted событие и GameFailed событие.

Итак, когда Player A хочу играть в игру с Player B, Player A получает RequestGame команда. Если этот игрок играет что-то еще, то PlayerAlreadyPlaysAGame исключение выдается, в противном случае оно поднимает GameRequested событие и обновить его внутреннее состояние как playing,

Game сага ловит GameRequested событие и отправить RequestGame командовать Player B совокупность (это Player объединяться с ID равно A). Затем:

  • Если Player B играет в другую игру (он знает это, запрашивая свой внутренний playing состояние) то поднимает GameRejected событие; Game сага ловит это событие и отправляет MarkGameAsRejected командовать Player A; затем Player A поднимает GameFailed событие и обновляет свое внутреннее состояние как not_playing,

  • Если Player B не играет в другую игру, то поднимает GameAccepted событие; Game сага ловит это событие и отправляет MarkGameAsAccepted командовать Player A совокупности; Player A затем испускает GameStarted событие и обновить его внутреннее состояние как playing,

Чтобы понять это, вы должны попытаться смоделировать сценарий использования так, как если бы не было компьютеров, а игроками были бы люди, которые общаются по печатной почте.

Это решение является масштабируемым, и я понимаю, что это необходимо.

Другое решение не представляется возможным для миллионов игроков.

Третье решение - использовать набор активных игроков в таблице SQL или NoSQL, без использования тактического шаблона Aggregate. Для большей ясности, когда вы устанавливаете пару игроков в качестве активных, вы можете использовать оптимистическую блокировку или транзакции, где это поддерживается (низкий уровень масштабирования) или двухфазные фиксации (что-то уродливое).

Другие вопросы по тегам