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

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

У меня есть 3 Объекта: План, PlannedRole и PlannedTraining. Каждый План может включать многие PlannedRoles и PlannedTrainings.

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

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

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

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

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

Любой совет значительно ценился бы.

С наилучшими пожеланиями, Mosh

6
задан Mosh 1 April 2010 в 06:13
поделиться

3 ответа

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

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

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

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

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

Правка - Пример:

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

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

Библиотека модели содержит совокупный класс со всеми задействованными инвариантами, а библиотека отчетов содержит этот простой класс:

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);
        }
    }
9
ответ дан 8 December 2019 в 14:42
поделиться

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

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


Возможно, вы захотите проверить так называемую архитектуру CQRS (разделение ответственности за запросы команд), которая идеально подходит для решения этой конкретной проблемы. Вот пример приложения Марка Нейхофа. Вот хороший список «начало работы» .

4
ответ дан 8 December 2019 в 14:42
поделиться

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

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

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

3
ответ дан 8 December 2019 в 14:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: