DDD: Совокупные корни - PullRequest
       50

DDD: Совокупные корни

6 голосов
/ 01 апреля 2010

Мне нужна помощь в поиске моего совокупного корня и границы.

У меня есть 3 объекта: Plan, PlannedRole и PlannedTraining. Каждый план может включать в себя много плановых ролей и плановых тренировок.

Решение 1. Сначала я подумал, что План - это совокупный корень, потому что PlannedRole и PlannedTraining не имеют смысла вне контекста Плана. Они всегда в рамках плана. Кроме того, у нас есть бизнес-правило, которое гласит, что в каждом плане может быть максимум 3 плановых роли и 5 плановых тренировок. Поэтому я подумал, что, назначив План в качестве совокупного корня, я смогу применить этот инвариант.

Однако у нас есть страница поиска, где пользователь ищет планы. Результаты показывают несколько свойств самого Плана (и ни одного из его PlannedRoles или PlannedTrainings). Я подумал, что если мне придется загружать весь агрегат, это будет иметь много накладных расходов. Есть около 3000 планов, и у каждого может быть несколько детей. Загрузка всех этих объектов вместе, а затем игнорирование PlannedRoles и PlannedTrainings на странице поиска не имеет смысла для меня.

Решение 2: Я только что понял, что пользователю нужны еще 2 страницы поиска, где они могут искать запланированные роли или запланированные тренинги. Это заставило меня понять, что они пытаются получить доступ к этим объектам независимо и «вне» контекста Плана. Поэтому я подумал, что был неправ в своем первоначальном дизайне, и именно так я придумал это решение. Итак, я подумал, что здесь будет 3 совокупности, по 1 на каждую сущность.

Этот подход позволяет мне искать каждый объект независимо, а также решает проблему производительности в решении 1. Однако, используя этот подход, я не могу применить инвариант, который я упоминал ранее.

Существует также другой инвариант, в котором говорится, что План может быть изменен, только если он имеет определенный статус. Поэтому я не смогу добавить какие-либо PlannedRoles или PlannedTrainings в план, который не находится в этом статусе. Опять же, я не могу применить этот инвариант при втором подходе.

Любой совет будет принят с благодарностью.

Ура, Мош

Ответы [ 3 ]

9 голосов
/ 03 апреля 2010

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

DDD - Как реализовать высокопроизводительные репозитории для поиска .

Когда дело доходит до поиска, я не работаю с «моделью», вместо этого у меня есть специализированные поисковые репозитории, которые возвращают объекты «Сводка» ... т.е. «PlanSummary». Это не что иное, как информационные объекты (их можно больше рассматривать как отчеты), и они не используются в транзакционном смысле - я даже не определяю их в своей библиотеке классов моделей. Создав эти специализированные репозитории и типы, я могу реализовать высокоэффективные поисковые запросы, которые могут содержать сгруппированные данные (например, счет PlannedTraining) без загрузки всех ассоциаций агрегата в память. После того, как пользователь выберет один из этих итоговых объектов в пользовательском интерфейсе, я смогу использовать идентификатор для извлечения фактического объекта модели, выполнения транзакций и фиксации изменений.

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

Таким образом, вы выполняете поиск по исполнителю, при этом сохраняя единый агрегат с необходимыми инвариантами.

Редактировать - Пример:

ОК, так что я думаю, что реализация субъективна, но именно так я и обработал ее в своем приложении, используя в качестве примера агрегат TeamMember. Пример написан на C #. У меня есть две библиотеки классов:

  • Модель
  • Отчетность

Библиотека Model содержит агрегатный класс со всеми принудительными инвариантами, а библиотека Reporting содержит этот простой класс:

public class TeamMemberSummary
{
    public string FirstName { get; set; }

    public string Surname { get; set; }

    public DateTime DateOfBirth { get; set; }

    public bool IsAvailable { get; set; }

    public string MainProductExpertise { get; set; }

    public int ExperienceRating { get; set; }
}

Библиотека отчетов также содержит следующий интерфейс:

public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary>
{

}

Это интерфейс, который прикладной уровень (который в моем случае оказывается службами WCF) будет использовать и разрешит реализацию через мой контейнер IoC (Unity). IReportRepository находится в библиотеке Infrastructure.Interface, как и базовая ReportRepositoryBase. Итак, в моей системе есть два разных типа репозитория - Агрегированные репозитории и репозитории отчетов ...

Затем в другой библиотеке, Repositories.Sql, у меня есть реализация:

public class TeamMemberSummaryRepository : ITeamMemberSummaryRepository
{
    public IList<TeamMemberSummary> FindAll<TCriteria>(TCriteria criteria) where TCriteria : ICriteria
    {
        //Write SQL code here

        return new List<TeamMemberSummary>();
    }

    public void Initialise()
    {

    }
}

Итак, на моем прикладном уровне:

    public IList<TeamMemberSummary> FindTeamMembers(TeamMemberCriteria criteria)
    {
        ITeamMemberSummaryRepository repository 
            = RepositoryFactory.GetRepository<ITeamMemberSummaryRepository>();

        return repository.FindAll(criteria);

    }

Затем на клиенте пользователь может выбрать один из этих объектов и выполнить действие с ним на прикладном уровне, например:

    public void ChangeTeamMembersExperienceRating(Guid teamMemberID, int newExperienceRating)
    {
        ITeamMemberRepository repository
            = RepositoryFactory.GetRepository<ITeamMemberRepository>();

        using(IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
        {
            TeamMember teamMember = repository.GetByID(teamMemberID);

            teamMember.ChangeExperienceRating(newExperienceRating);

            repository.Save(teamMember);
        }
    }
4 голосов
/ 10 апреля 2010

Реальная проблема здесь - нарушение SRP. Ваша входная часть приложения конфликтует с выходной.

Придерживайтесь первого решения (План == совокупный корень). Искусственное продвижение сущностей (или даже ценностных объектов) для объединения корней искажает всю модель предметной области и разрушает все.


Возможно, вы захотите проверить так называемую архитектуру CQRS (разделение ответственности по командным запросам), которая идеально подходит для решения этой конкретной проблемы. Вот пример приложения от Mark Nijhof. Вот хороший список Getting-Start * .

3 голосов
/ 14 апреля 2010

В этом весь смысл архитектуры CQRS : отделить команды - которые изменяют домен - от запросов - которые просто дают представление о состоянии домена, потому что требования для команд и запросов очень разные.

Вы можете найти хорошие введения в этих блогах:

и во многих других блогах (включая мой )

...