Должна ли одна команда адресовать несколько агрегатов? - PullRequest
2 голосов
/ 13 января 2020

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

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

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

Чтобы привести простой пример, чтобы лучше проиллюстрировать сценарий:

  • Пользователь должен присоединиться к группе.
  • У пользователя максимальное количество групп.
  • В группе максимальное количество пользователей.

Куда поместить команду, запускающую начальный процесс присоединения, и как это назвать? user.join(group) чувствует себя так же, как и group.welcome(user). Я бы, вероятно, go для первого, потому что это ближе к вездесущему языку, но в любом случае ...

Если бы у меня было что-то выше агрегатов, например, вышеупомянутые сервисы, то я мог бы запустить что-то вроде :

userManagement.addUserToGroup(user, group);

Однако эта функция addUserToGroup должна будет вызывать обе команды, что, в свою очередь, означает, что она должна позаботиться об обработке обеих команд - что несколько противоречит интуитивно понятному наличию отдельных агрегатов вообще и наличие агрегатов в качестве транзакционных границ.

Как правильно будет смоделировать это?

Ответы [ 2 ]

3 голосов
/ 13 января 2020

Возможно, стоит рассмотреть Грега Янга на Окончательная согласованность и проверка набора .

Какое влияние на бизнес имеет отказ

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

И, конечно, Пэт Хелланд из Воспоминания, догадки и извинения .

Краткая версия: два генерала говорят нам, что, если две части информации должны быть согласованными, нам нужно записать обе части информации в одном месте , «Инвариант» ограничивает нашу модель данных.

Инвариант, который вы описываете, фактически является парой проблем проверки набора: коллекция «членство» допускает только столько членов с пользователем A и только столько членов с группой B И если вы действительно находитесь в ситуации «мы go вне бизнеса, если эти правила нарушены», то вы не можете распространять членов этого набора - вы должны заблокировать весь набор, когда вы измените его, чтобы правило не нарушалось и победил первый писатель.

Элемент, который требует некоторого внимания при моделировании: является ли модель предметной области авторитетом для членства? или "реальный мир" отвечает за членство, а модель домена просто кэширует эту информацию для последующего использования? Вы хотите быть очень осторожными при попытке применить инвариант в реальном мире.

Существует риск того, что вы в конечном итоге ограничите порядок, в котором информация принимается моделью.

1 голос
/ 14 января 2020

По сути, у вас есть много-много отношений между пользователями и группами с ограничениями с обеих сторон:

  • Ограничение на количество групп, к которым пользователь может присоединиться
  • Ограничение на количество пользователей, которых может иметь группа

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

Обеспечение согласованности с такими ограничениями как для сущностей Group , так и User будет затруднено. в одной операции из-за параллелизма операций.

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

Что вы можете сделать, это ввести другая концепция в вашем домене. Понятие «слот» в группе . «Слоты» ограничены максимальным количеством Слотов для Группы .

Тогда Пользователь выдаст JoinGroupRequest , которое может быть Принято или Отклонено .

A Слот может быть Занято или Зарезервировано . Затем вы можете представить концепцию SlotReservation . Процесс присоединения пользователя к группе будет следующим:

  • Выпуск JoinGroupRequest от пользователя
  • Попробуйте Зарезервировать a Слот с соблюдением ограничения MaxUsersPerGroup .
  • Получить a Slot или Отклонить SlotReservation пользователя , обеспечивающего ограничение MaxGroupsPerUser .
  • Принять или Отклонить JoinGroupRequest в зависимости от результата SlotReservation

Если SlotReservation is Отклонено , другой Пользователь сможет использовать этот Слот позже.

Для реализации вы можете добавить Очередь SlotReservation на группу , чтобы гарантировать, что после освобождения Slot после Rejected SlotReservation , следующего пользователя , который хочет присоединиться к Group

. Для реализации вы можете добавить коллекцию Слотов в Группу или сделать Слот агрегат сам по себе.

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

По сути, эта операция становится Предварительной операцией .

Для получения более подробной информации см. Образец подотчетности и Жизнь за пределами распределенных транзакций и мнение отступника и Жизнь за распределенными транзакциями за реализацию отступника .

...