#domain-driven-design
#проектирование, управляемое доменом
Вопрос:
Я хотел смоделировать домен простой игры.
У нас есть игрок, который может покупать разные здания, у него также есть акции, и у игрока могут быть другие вещи, которые я собираюсь добавить в будущем. Но сейчас я сосредоточен только на зданиях и складе.
Я решил разделить его на три агрегата: Player, PlayerBuildingManagment, PlayerStock. Я не уверен, что это правильное разделение, потому что Stock и PlayerBuildingManagment очень связаны с Player, поэтому, возможно, это не должны быть агрегированные корни. С другой стороны, я не хочу создавать один огромный агрегат. В моем случае параллелизм не будет проблемой, потому что все, что есть на складе, управление зданием будет изменяться только одним игроком.
Игрок
Запас 1. AddToStock() 2. TakeFromStock()
Управление созданием игроков 1. AddBuilding()
Теперь мне нужно реализовать процесс, в котором игрок будет покупать здание, поэтому мне нужно
- Проверьте, достаточно ли у него ресурсов, и возьмите их из запаса.TakeFromStock()
- Мне нужно добавить building PlayerBuildingManagment.AddBuilding().
- Сохранение данных.
Я могу реализовать этот процесс внутри одной сервисной функции, но из того, что я знаю, мы не должны изменять состояние двух агрегатов внутри одной транзакции. Как вы думаете, как должна быть реализована эта коммуникация. Может быть, мне следует внедрить stock и buildingmanagment внутри player aggregate. Или создайте какой-нибудь диспетчер процессов для осуществления этой коммуникации. Не могли бы вы дать мне какую-нибудь подсказку.
Комментарии:
1. Для однопоточных процессов, где нет разногласий, я думаю, на самом деле не имеет значения, каковы ваши границы. Правило не изменять несколько агрегатов в одной транзакции заключается в уменьшении количества транзакционных сбоев, которых здесь нет. Кроме того, вам даже не нужен какой-либо механизм блокировки, потому что нет никаких возможных условий гонки.
Ответ №1:
Получение согласованности транзакций между агрегатами, которые, по определению, являются их собственными границами согласованности, по меньшей мере проблематично.
Давайте исходить из вашего представления о том, что есть игроки, PlayerStock — который действует как банк, и PlayerBuildingManager — который действует как подрядчик.
Что касается проблемы проверки наличия достаточного запаса и запроса на сборку здания Y игроком P за стоимость X, я бы, вероятно, отправил агрегату PlayerStock единственную команду, которая принимает эти параметры:
- идентификатор игрока, P,
- количество запаса, которое нужно взять, X и,
- что делать, если инвентаризация прошла успешно:
Здесь вызывающий это сообщение для PlayerStock предоставит сообщение «что делать», которое фактически представляет сообщение для отправки кому-либо еще в случае успеха (у вас также может быть «что делать» в случае неудачи, но это другой вопрос).
В этом случае параметр сообщения «что делать» будет кодировать команду для построения здания Y для игрока X, которая должна быть отправлена в PlayerBuildingManager.
Остальное может быть обработано с возможной согласованностью, типичной для нескольких агрегатов.
Таким образом, некоторый контроллер через пользовательский интерфейс отправляет PlayerStock сообщение, чтобы вычесть некоторое количество X из игрока P и отправить некоторое сообщение об успехе. В случае успеха он выполнит сообщение об успешном завершении, которое представляет собой сообщение для PlayerBuildingManager о необходимости построить здание Y для игрока P. В конечном итоге будет период согласованности, за который запасы были вычтены, но здание еще не материализовано. Это конечная согласованность, которую вы получаете при использовании нескольких агрегатов.
Причина кодирования сообщения об успешном завершении в качестве параметра заключается в том, чтобы вы могли более свободно связывать PlayerStock из каталога вещей, которые могут быть сконструированы, и тем, кто может их сконструировать. Произвольное сообщение об успешном завершении в качестве параметра обеспечит высокую степень гибкости без необходимости для игроков слишком много знать.
(Если вы не хотите иметь с этим дело, вы, конечно, можете поместить их все в один и тот же агрегат.)
Если вы хотите пойти дальше:
Допустим, что PlayerBuildingManager не удалось построить здание в команде, которую он получил от PlayerStock. Ну, это должно вернуть запас. Но чтобы сохранить слабую связь, PlayerStock может предоставить сообщение для отправки в этом случае, которое возвращает X в PlayerStock для P.
Также вы можете подумать о том, что произойдет, если строительство здания занимает не только время ожидания, но и время в реальном времени (как это было бы в реальном мире). В этом случае игрок может захотеть отменить строительство, и ему может быть разрешено или не разрешено это сделать, и, возможно, с полным возмещением или частичным возмещением…
Комментарии:
1. То, что вы описываете, кажется, лучше всего обрабатывается менеджером процессов, известным как Saga.