Основной совокупный вопрос
Разрешено ли клиентскому коду ссылаться на объекты внутри агрегата, который не является корневым? у меня есть Story
(Корень), Team
(Сущность) и TeamMember
(Сущность). Я пытаюсь решить, если AddTeamMember
метод принадлежит Team
или же Story
,
Я думаю, что мой пример был немного ошибочным. Мой реальный вопрос: может ли код клиента ссылаться на некорневые объекты в совокупности?
4 ответа
Мое мнение - так не должно быть. Наличие ссылки на сущность, принадлежащую определенной совокупности, означает, что вы можете вызывать метод для этой сущности без полного контекста совокупности, и если вы разрешаете это, вы никогда не сможете быть уверены, что весь агрегат действителен и согласован.
Простой пример:
public class MyAggregateRoot
{
protected MyEntity entity;
public void BuildUpAggregate()
{
ValidateSomeRule();
LoadEntityFromDatabase();
}
public MyEntity MyEntity
{
get
{
VerifySomeOtherRule();
return entity;
}
}
}
Как вы можете видеть, при построении и извлечении MyEntity через агрегатный корень мы проверили два правила проверки - если вы позволите клиенту напрямую ссылаться на MyEntity, агрегат может измениться во времени между клиентом, который извлек сущность и выполнил над ней операцию, поэтому проверки больше не будут действительными, но вы не узнаете об этом факте. Другими словами, ваш агрегат будет непоследовательным и потенциально недействительным.
В общем, в DDD существует строгое правило, которое гласит, что весь доступ к объектам в совокупности должен осуществляться путем обхода от корня агрегата - это правило именно для согласованности агрегатов.
Тем не менее, ваш клиент может ссылаться на проекцию объекта - своего рода копия только для чтения, которая содержит только данные, необходимые для отображения и принятия решения о том, доступно ли определенное действие в текущем контексте. Вы можете пойти еще дальше и объединить данные из набора объектов в одну проекцию, немного настроить ее, чтобы она была адаптирована к требованиям пользовательского интерфейса. Я нашел эту технику весьма полезной для того, чтобы не позволить моему пользовательскому интерфейсу решать, как следует моделировать бизнес-домен.
Я не уверен насчет "корневых" или "совокупных" правил, но не думаю, что история должна знать что-либо о том, как управляется членство в команде.
У команды должны быть отношения с членом команды. где история и член команды не имеют прямого отношения друг к другу.
ваш класс должен выглядеть так:
class team
{
list<TeamMember> teamMembers;
void AddTeamMember(TeamMember member){}
}
Еще одна причина, по которой в команде должен быть добавлен метод члена группы, если этот метод является частью истории, вам нужно явно указать, в какую команду вы хотите добавить члена.
addteamMember(Team T, TeamMember member);
Корень должен предоставлять надлежащие методы для доступа к некорневым элементам (первый уровень вложенности). В вашем случае команда должна иметь метод addTeamMember, а история должна иметь метод (AddMemberToTeam(team t, teammember m)), который вызывает метод addTeamMember указанной команды.
Не зная, как ваша архитектура должна работать, похоже, что она должна продолжаться Team
вместо Story
, Это при условии, что у вас есть 1 ко многим из Team
в Team Member
и что Team Member
это дитя Team
объект, а не Story
,