Скажем, у нас есть система регистрации задач, когда задача регистрируется, пользователь указывает категорию, и задача по умолчанию имеет статус «Не выполнено». Предположим, что в этом случае Category и Status должны быть реализованы как объекты. Обычно я бы сделал это:
Уровень приложений:
public class TaskService
{
//...
public void Add(Guid categoryId, string description)
{
var category = _categoryRepository.GetById(categoryId);
var status = _statusRepository.GetById(Constants.Status.OutstandingId);
var task = Task.Create(category, status, description);
_taskRepository.Save(task);
}
}
Сущность:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Я делаю это так, потому что мне постоянно говорят, что сущности не должны получать доступ к репозиториям, но для меня было бы гораздо больше смысла, если бы я сделал это:
Сущность:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
Хранилище состояния в любом случае внедряется в зависимость, поэтому реальной зависимости нет, и мне кажется, что именно домен принимает решение о том, что задача по умолчанию не выполнена. В предыдущей версии создается впечатление, что именно уровень приложений принимает это решение. Любой, почему контракты репозитория часто в домене, если это не должно быть возможным?
Вот более крайний пример, здесь домен решает срочность:
Сущность:
public class Task
{
//...
public static void Create(Category category, string description)
{
var task = new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
if(someCondition)
{
if(someValue > anotherValue)
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
Нет способа передать все возможные версии Urgency, и нет способа вычислить эту бизнес-логику на прикладном уровне, поэтому, несомненно, это будет наиболее подходящим способом?
Так является ли это действительной причиной для доступа к репозиториям из домена?
РЕДАКТИРОВАТЬ: Это также может иметь место в случае нестатических методов:
public class Task
{
//...
public void Update(Category category, string description)
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
if(someCondition)
{
if(someValue > anotherValue)
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
источник
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
являющаяся бизнес-правилом , которую вы могли бы прочитать как «Бизнес диктует, что первоначальный статус всех задач будет невыполненным», и именно поэтому эта строка кода не принадлежит внутри репозитория, единственной задачей которого является управление данными с помощью операций CRUD.Я не знаю, является ли ваш пример статуса реальным кодом или здесь просто для демонстрации, но мне кажется странным, что вы должны реализовывать Status как сущность (не говоря уже о Aggregate Root), когда его ID является константой, определенной в коде -
Constants.Status.OutstandingId
. Разве это не противоречит цели «динамических» статусов, которые вы можете добавить в базу данных, сколько хотите?Я бы добавил, что в вашем случае создание
Task
(включая получение правильного статуса из StatusRepository, если это необходимо) может заслуживать,TaskFactory
а не оставатьсяTask
само по себе, поскольку это нетривиальная совокупность объектов.Но :
Это утверждение является в лучшем случае неточным и слишком упрощенным, в худшем - вводящим в заблуждение и опасным.
В архитектуре, управляемой доменом, довольно широко принято, что сущность не должна знать, как хранить себя - это принцип невежества постоянства. Так что нет вызовов в его хранилище, чтобы добавить себя в хранилище. Должен ли он знать, как (и когда) хранить другие объекты ? Опять же, эта ответственность, похоже, принадлежит другому объекту - возможно, объекту, который осведомлен о контексте выполнения и общем прогрессе текущего варианта использования, например, сервис уровня приложения.
Может ли объект использовать хранилище для извлечения другого объекта ? В 90% случаев это не обязательно, так как сущности, в которых они нуждаются, обычно находятся в области его совокупности или могут быть получены путем обхода других объектов. Но бывают случаи, когда это не так. Например, если вы берете иерархическую структуру, сущности часто должны получить доступ ко всем своим предкам, конкретному внуку и т. Д. Как часть их внутреннего поведения. У них нет прямой ссылки на этих отдаленных родственников. Было бы неудобно передавать этих родственников им в качестве параметров операции. Так почему бы не использовать репозиторий, чтобы получить их - при условии, что они являются совокупными корнями?
Есть несколько других примеров. Дело в том, что иногда есть поведение, которое вы не можете поместить в службу домена, так как оно, кажется, идеально вписывается в существующую сущность. И все же, эта сущность должна иметь доступ к хранилищу, чтобы увлажнить корень или набор корней, которые не могут быть переданы ему.
Таким образом, доступ к хранилищу от сущности сам по себе неплохой , он может принимать различные формы, возникающие в результате различных проектных решений, от катастрофических до приемлемых.
источник
Это одна из причин, по которой я не использую Enums или таблицы чистого просмотра в своем домене. Срочность и статус являются состояниями, и существует логика, связанная с состоянием, которое напрямую связано с этим состоянием (например, в какие состояния я могу переходить, учитывая мое текущее состояние). Кроме того, записывая состояние как чистое значение, вы теряете информацию, например, как долго задача находилась в данном состоянии. Я представляю статусы как иерархию классов. (В C #)
Реализация CompletedTaskStatus была бы почти такой же.
Здесь следует отметить несколько вещей:
Я делаю конструкторы по умолчанию защищенными. Это так, что фреймворк может вызывать его при извлечении объекта из постоянства (и EntityFramework Code-first, и NHibernate используют прокси-серверы, полученные из ваших доменных объектов, для создания своей магии).
Многие из установщиков свойств защищены по той же причине. Если я хочу изменить дату окончания интервала, я должен вызвать функцию Interval.End () (это является частью доменного дизайна, обеспечивающего значимые операции, а не анемичные доменные объекты).
Я не показываю это здесь, но Задача также скрыла бы детали того, как она сохраняет свой текущий статус. У меня обычно есть защищенный список HistoricalStates, который я позволяю общественности запрашивать, если они заинтересованы. В противном случае я выставляю текущее состояние как метод получения, который запрашивает HistoricalStates.Single (state.Duration.End == null).
Функция TransitionTo важна, потому что она может содержать логику о том, какие состояния допустимы для перехода. Если у вас просто перечисление, эта логика должна лежать в другом месте.
Надеюсь, это поможет вам немного лучше понять подход DDD.
источник
Я пытался решить эту проблему в течение некоторого времени, я решил, что хочу иметь возможность вызывать Task.UpdateTask (), хотя я бы предпочел, чтобы это зависело от конкретного домена, в вашем случае, возможно, я бы назвал его Task.ChangeCategory (...) для обозначения действия, а не только CRUD.
В любом случае, я попробовал твою проблему и придумал это ... возьми мой пирог и съешь его тоже. Идея состоит в том, что действия происходят с сущностью, но без внедрения всех зависимостей. Вместо этого работа выполняется в статических методах, чтобы они могли получить доступ к состоянию объекта. Фабрика собирает все вместе и, как правило, будет иметь все, что ей нужно для выполнения работы, которую должна делать организация. Клиентский код теперь выглядит чистым и понятным, и ваша сущность не зависит от какого-либо внедрения в хранилище.
источник