Я также изучаю DDD, и несколько дней назад я задаю вопрос о похожей проблеме, которую вы можете найти здесь .
Я понял, что единственный реальный ответ: это зависит .Нет правильных или неправильных подходов как таковых , все должно служить цели бизнес-проблемы и ее решению.Хотя есть руководящие принципы и практические правила, которые могут быть очень полезны, например:
- Не моделируйте реальность.Модель предметной области - это абстракция , предназначенная для решения конкретной проблемы.
- Не моделируйте на основе взаимосвязи данных.Каждая ассоциация должна быть там для обеспечения соблюдения правила или инварианта, а не потому, что эти объекты связаны в реальной жизни.Начните моделирование на основе поведения.
- Предпочитайте небольшие агрегаты.
- Предпочитайте изменять один агрегат одновременно (случай использования / транзакция) и используйте конечную согласованность для обновления других агрегатов.
- Создавайте связи между агрегатами только по идентичности.
Я думаю, что проблема в вашем сценарии заключается в том, что многое отсутствует.Кому принадлежит эта ассоциация и почему?Есть ли другой вариант использования, охватывающий как студента, так и курс?Почему вы ставите student.SubscribeTo(course)
вместо course.enroll(student)
?Помните, что целью DDD является решение сложной логики предметной области, чтобы она сияла при приближении к стороне записи модели, стороне чтения может быть выполнено многими различными способами без больших затратсвязей в модели.
Для того, что вы сказали, просто проверка возраста, вероятно, не является инвариантом, который требует создания большой совокупности:
Если я изменю Course.MinAge
в некоторых других местах моего кода я не нарушаю свои бизнес-требования, так как хочу, чтобы возраст соблюдался только при подписке на курс, и я не против, если позже Course.MinAge
изменится.
Тогда нет оснований для принудительного обеспечения постоянства согласованности Student
и Course
(в данном конкретном контексте / сценарии), и нет необходимости включать их в одну и ту же совокупность.Если единственное правило, которое вам нужно соблюдать, это Student.Age >= Course.MinAge
, вы можете придерживаться простого:
Student.SubscribeTo(Course course)
, где Student
и Course
не являются частью одного и того же агрегата.Нет ничего против загрузки двух разных агрегатов и использует их в одной транзакции, , пока вы изменяете только один .(Что ж, нет ничего против изменения двух агрегатов в одной и той же транзакции, это просто практическое правило, но, вероятно, вам не нужно разбивать его в этом случае).
Здесь Student.SubscribeTo
принудительно выполнитправило относительно возраста.Я должен сказать, что это звучит «странно», чтобы позволить Student
подтвердить свой собственный возраст, но, возможно, это как раз в вашей модели (помните, не моделируйте реальность, моделируйте решения).В результате у Student
будет новое состояние, содержащее идентификатор курса, а Course
останется неизменным.
Другой случай, если требования бизнеса указывают: когда Course.MinAge меняет уже зачисленных студентовкурс должен быть удален из курса, если Student.Age < Course.MinAge
.
Здесь вы должны сначала ответить (с помощью эксперта по домену) еще на несколько вопросов: Почему они должны быть удалены?Должны ли они быть удалены немедленно?Что если они посещают занятия в этот момент?Что означает удаление студента?
Скорее всего, в домене нет реальной необходимости удалять учеников одновременно с изменением MinAge (например, когдаоперация считается успешной только тогда, когда все происходит, а если нет, то ничего не происходит).Студенты, возможно, должны войти в новое состояние, которое может быть решено в конечном счете.Если это так, то вам также не нужно включать их в одну совокупность.
Отвечая на вопрос в названии, неудивительно: это зависит .Вся причина иметь агрегат состоит в том, чтобы защитить инварианты каким-то образом связанного набора сущностей.Совокупность не является HAS-A
отношением (необязательно).Если вам нужно защитить инвариант, охватывающий несколько сущностей, вы можете сделать их совокупными и выбрать сущность в качестве совокупного корня;этот корень является единственной точкой доступа для изменения агрегата, поэтому каждый инвариант всегда применяется.Разрешение прямой ссылки на сущность внутри агрегата нарушает эту защиту: теперь вы можете изменять эту сущность без ведома корня.Поскольку сущности внутри агрегата не доступны извне, эти сущности имеют только локальную идентичность и не имеют значения как автономные объекты без ссылки на корень.Тем не менее, можно запросить у корня сущности внутри агрегата.
Вы можете , иногда передавать ссылку на внутреннюю сущность в другой агрегат, если это временная ссылкаи никто не изменяет эту ссылку вне совокупного корня.Это, однако, запутывает модель и границы становятся размытыми.Лучшим подходом является передача копии этой сущности или, что лучше, передача неизменного представления этой сущности (вероятно, объекта значения), чтобы не было возможности нарушить инварианты.Если вы думаете, что нет инварианта, который нужно разбить, передавая ссылку на внутреннюю сущность, то, возможно, нет никакой причины иметь агрегат для начала.