DDD: Совокупные корни
Мне нужна помощь в поиске моего совокупного корня и границы.
У меня есть 3 объекта: Plan, PlannedRole и PlannedTraining. Каждый план может включать в себя много плановых ролей и плановых тренировок.
Решение 1: Сначала я думал, что План является совокупным корнем, потому что PlannedRole и PlannedTraining не имеют смысла вне контекста Плана. Они всегда в рамках плана. Кроме того, у нас есть бизнес-правило, которое гласит, что в каждом плане может быть максимум 3 плановых роли и 5 плановых тренировок. Поэтому я подумал, что, назначив План в качестве совокупного корня, я смогу применить этот инвариант.
Тем не менее, у нас есть страница поиска, где пользователь ищет планы. Результаты показывают несколько свойств самого Плана (и ни одного из его PlannedRoles или PlannedTrainings). Я подумал, что если мне придется загружать весь агрегат, это будет иметь много накладных расходов. Есть около 3000 планов, и у каждого может быть несколько детей. Загрузка всех этих объектов вместе, а затем игнорирование PlannedRoles и PlannedTrainings на странице поиска не имеет смысла для меня.
Решение 2. Я только что понял, что пользователю нужны еще 2 страницы поиска, где они могут искать запланированные роли или запланированные тренинги. Это заставило меня понять, что они пытаются получить доступ к этим объектам независимо и "вне" контекста Плана. Поэтому я подумал, что был неправ в своем первоначальном дизайне, и именно так я придумал это решение. Итак, я подумал, что здесь будет 3 совокупности, по 1 на каждую сущность.
Этот подход позволяет мне искать каждую сущность независимо, а также решает проблему производительности в решении 1. Однако, используя этот подход, я не могу применить инвариант, который я упоминал ранее.
Существует также другой инвариант, в котором говорится, что план может быть изменен, только если он имеет определенный статус. Поэтому я не смогу добавить какие-либо PlannedRoles или PlannedTrainings в план, который не находится в этом статусе. Опять же, я не могу применить этот инвариант при втором подходе.
Любой совет будет принята с благодарностью.
Ура, мош
3 ответа
У меня были похожие проблемы с этим при разработке моей модели, и я задал этот вопрос, который, я думаю, мог бы помочь вам, особенно в отношении вашего первого замечания.
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);
}
}
Настоящая проблема здесь - нарушение SRP. Ваша входная часть приложения конфликтует с выходной.
Придерживайтесь первого решения (план == совокупный корень). Искусственное продвижение сущностей (или даже объектов стоимости) для агрегирования корней искажает всю модель предметной области и разрушает все.
Возможно, вы захотите проверить так называемую архитектуру CQRS (разделение ответственности по командным запросам), которая идеально подходит для решения этой конкретной проблемы. Вот пример приложения от Mark Nijhof. Вот хороший список для начала.
В этом весь смысл архитектуры CQRS: отделить команды - которые изменяют домен - от запросов - которые просто дают представление о состоянии домена, потому что требования для команд и запросов очень разные.
Вы можете найти хорошие введения в этих блогах:
и на многих других блогах (включая мой)