Простой совокупный корень и хранилище - PullRequest
5 голосов
/ 07 февраля 2011

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

У меня есть две сущности ProcessType и Process. Process не может существовать без ProcessType, а ProcessType имеет множество Process es. Таким образом, процесс содержит ссылку на тип и не может существовать без него.

Так должен ли ProcessType быть совокупным корнем? Новые процессы будут созданы с помощью вызова processType.AddProcess(new Process()); Однако у меня есть другие объекты, которые содержат только ссылку на Process и получают доступ к ее типу через Process.Type. В этом случае нет смысла проходить сначала ProcessType.

Но объектам AFAIK за пределами агрегата разрешено хранить только ссылки на корень агрегата, а не объекты внутри агрегата. Итак, у меня есть два агрегата, каждый со своим репозиторием?

Ответы [ 3 ]

15 голосов
/ 08 февраля 2011

Я в значительной степени согласен с тем, что сказал Сизиф, особенно с тем, что нельзя ограничивать себя «правилами» DDD, которые могут привести к довольно нелогичному решению.

С точки зрения вашей проблемы я пришелмного раз, и я бы назвал ProcessType как lookup .Поиски являются объектами, которые «определяют» и имеют нет ссылок на другие объекты;в терминологии DDD они являются объектами значения.Другими примерами того, что я бы назвал поиском, может быть RoleType члена команды, который может быть, например, тестером, разработчиком, менеджером проекта.Даже «Заголовок» человека я бы определил как поиск - мистер, мисс, миссис, доктор

Я бы смоделировал вашу совокупность процессов следующим образом:объектов, как правило, должны заполнять раскрывающиеся списки в пользовательском интерфейсе и, следовательно, нужен собственный механизм доступа к данным.Тем не менее, я лично НЕ создал «репозитории» как таковые для них, а скорее «LookupService».Для меня это сохраняет элегантность DDD, сохраняя «репозитории» строго для агрегатных корней.

Вот пример обработчика команд на моем сервере приложений и как я реализовал это:

TeamАгрегат членов:

public class TeamMember : Person
{
    public Guid TeamMemberID
    {
        get { return _teamMemberID; }
    }

    public TeamMemberRoleType RoleType
    {
        get { return _roleType; }
    }

    public IEnumerable<AvailabilityPeriod> Availability
    {
        get { return _availability.AsReadOnly(); }
    }
}

Обработчик команд:

public void CreateTeamMember(CreateTeamMemberCommand command)
{
    TeamMemberRoleType role = _lookupService.GetLookupItem<TeamMemberRoleType>(command.RoleTypeID);

    TeamMember member = TeamMemberFactory.CreateTeamMember(command.TeamMemberID,
                                                           role,
                                                           command.DateOfBirth,
                                                           command.FirstName,
                                                           command.Surname);

    using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
        _teamMemberRepository.Save(member);
}

Клиент также может использовать LookupService для заполнения раскрывающихся списков и т. Д .:

ILookup<TeamMemberRoleType> roles = _lookupService.GetLookup<TeamMemberRoleType>();
10 голосов
/ 07 февраля 2011

Не все так просто. ProcessType больше всего похож на объект уровня знаний - он определяет определенный процесс. Процесс, с другой стороны, является экземпляром процесса, который является ProcessType. Вы, вероятно, действительно не нуждаетесь или не хотите двунаправленных отношений. Процесс, вероятно, не является логическим потомком ProcessType. Обычно они принадлежат чему-то другому, например, продукту, фабрике или последовательности.

Также по определению, когда вы удаляете корень агрегата, вы удаляете всех участников агрегата. Когда вы удаляете Process, я серьезно сомневаюсь, что вы действительно хотите удалить ProcessType. Если вы удалили ProcessType, возможно, вы захотите удалить все процессы этого типа, но эта связь уже не идеальна, и есть вероятность, что вы не будете удалять объекты определений, как только у вас будет исторический процесс, определенный ProcessType.

Я бы удалил коллекцию Processes из ProcessType и нашел бы более подходящего родителя, если таковой существует. Я бы оставил ProcessType в качестве члена Process, поскольку он, вероятно, определяет Process. Объекты операционного уровня (Process) и уровня знаний (ProcessType) редко работают как один агрегат, поэтому я бы выбрал либо Process быть агрегатным корнем, либо, возможно, нашел бы агрегатный корень, который является родительским для процесса. Тогда ProcessType будет внешним классом. Process.Type, скорее всего, избыточен, поскольку у вас уже есть Process.ProcessType. Просто избавься от этого.

У меня есть похожая модель для здравоохранения. Существует Процедура (Операционный уровень) и ПроцедураТип (уровень знаний). ПроцедураType является отдельным классом. Процедура является потомком третьего объекта Encounter. Encounter является совокупным корнем для процедуры. Процедура имеет ссылку на тип_процесса, но это один из способов. ПроцедураType - это объект определения, который не содержит коллекцию процедур.

РЕДАКТИРОВАТЬ (потому что комментарии настолько ограничены)

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

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

Итак, каковы основные понятия здесь:

Агрегаты предоставляют средства, позволяющие сделать сложную систему более управляемой за счет сокращения поведения многих объектов до поведения ключевых игроков более высокого уровня.

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

Давайте рассмотрим последний пункт. Во многих обычных приложениях кто-то создает набор объектов, которые не полностью заполнены, потому что им нужно только обновить или использовать несколько свойств. Приходит следующий разработчик, ему тоже нужны эти объекты, и кто-то уже сделал набор где-то по соседству для другой цели. Теперь этот разработчик решает просто использовать их, но потом обнаруживает, что они не обладают всеми необходимыми ему свойствами. Поэтому он добавляет еще один запрос и заполняет еще несколько свойств. В конце концов, потому что команда не придерживается ООП, потому что они придерживаются общего мнения, что ООП «неэффективно и нецелесообразно для реального мира и вызывает проблемы с производительностью, такие как создание полных объектов для обновления одного свойства». В итоге они получают приложение, полное встроенного кода SQL и объектов, которые по существу случайным образом материализуются где угодно. Хуже того, эти объекты являются недействительными проклятыми прокси. Процесс представляется процессом, но это не так, он частично заполняется различными способами в зависимости от того, что было необходимо. В результате вы получаете множество запросов для непрерывного частичного заполнения объектов в разной степени, а зачастую и множества посторонних дерьмов, таких как нулевые проверки, которые не должны существовать, но требуются, потому что объект никогда не является действительно действительным и т. Д.

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

Поэтому, когда Эванс говорит о создании репозиториев только для агрегатов, он говорит, что создайте агрегаты в допустимом состоянии и сохраняйте их таким образом, вместо того чтобы напрямую обходить агрегат для внутренних объектов. У вас есть процесс в качестве корневого агрегата, поэтому вы создаете хранилище. ProcessType не является частью этого агрегата. Чем ты занимаешься? Хорошо, если объект сам по себе и является сущностью, это совокупность 1. Вы создаете хранилище для него.

Теперь придет пурист и скажет, что у вас не должно быть этого хранилища, потому что ProcessType является объектом значения, а не сущностью. Поэтому ProcessType вообще не является агрегатом, и поэтому вы не создаете для него хранилище. Ну так что ты делаешь? То, что вы не делаете, - это добавляете ShootHourn ProcessType в какую-то искусственную модель только по той причине, что вам нужно получить ее, поэтому вам нужен репозиторий, но чтобы иметь репозиторий, вы должны иметь сущность в качестве совокупного корня. Что вы делаете, это тщательно продумайте концепции. Если кто-то говорит вам, что хранилище неверно, но вы знаете, что оно вам нужно, и что бы он ни говорил, ваша система хранилища действительна и сохраняет ключевые концепции, вы сохраняете хранилище как есть, а не деформируете свою модель, чтобы удовлетворить догму.

Теперь в этом случае, если я правильно понял, что такое ProcessType, как заметил другой комментатор, на самом деле это объект значения. Вы говорите, что это не может быть Объектом Значения. Это может быть по нескольким причинам. Возможно, вы говорите это, потому что вы используете, например, NHibernate, но модель NHibernate для реализации объектов-значений в той же таблице, что и другой объект, не работает. Таким образом, ваш ProcessType требует идентификатора столбца и поля. Часто из соображений базы данных единственной практической реализацией является создание объектов значений с идентификаторами в их собственной таблице. Или, может быть, вы так говорите, потому что каждый процесс указывает на один тип процесса по ссылке.

Это не имеет значения.Это значение объекта из-за концепции.Если у вас есть 10 объектов Process с одинаковым ProcessType, у вас есть 10 членов и значений Process.ProcessType.Независимо от того, указывает ли каждый Process.ProcessType на одну ссылку или каждая получает копию, они все равно по определению должны быть абсолютно одинаковыми и полностью взаимозаменяемыми с любым другим 10. Это то, что делает его значением Object.Человек, который говорит: «У него есть Id, следовательно, не может быть значением. У вас есть сущность» делает догматическую ошибку.Не делайте ту же ошибку, если вам нужно поле идентификатора, дайте его, но не говорите, что «это не может быть объект значения», хотя на самом деле это хотя и то, что по другой причине вам пришлось дать идентификаторк.

Так как вы понимаете это правильно и неправильно?ProcessType - это объект-значение, но по какой-то причине он должен иметь идентификатор.Идентификатор per se не нарушает правила.Вы понимаете это правильно, имея 10 процессов, которые имеют одинаковый ProcessType.Может быть, у каждого есть локальная глубокая копия, может быть, все они указывают на один объект.но каждый из них в любом случае идентичен, следовательно, каждый имеет Id = 2, например.Когда вы делаете это, вы ошибаетесь: каждый из 10 процессов имеет ProcessType, и этот ProcessType идентичен и полностью взаимозаменяем, за исключением того, что каждый из них также имеет свой уникальный идентификатор.Теперь у вас есть 10 экземпляров одного и того же, но они различаются только по Id и всегда будут отличаться только по Id.Теперь у вас больше нет объекта Value, не потому, что вы дали ему Id, а потому, что вы дали ему Id с реализацией, отражающей природу сущности - каждый экземпляр уникален и отличается

Имеет смысл?

0 голосов
/ 07 февраля 2011

Слушай, я думаю, ты должен реструктурировать свою модель.Используйте ProcessType как объект Value и Process Agg Root.Таким образом, у каждого процесса есть processType

Public class Process
{
      Public Process()
      {

      }

      public ProcessType { get; }

}

, для этого вам нужен только один корень agg, а не 2.

...