Как должно быть выражено добавление совокупного корня в репозиторий?
-
21-09-2019 - |
Вопрос
Допустим, у нас есть совокупная корневая сущность типа Заказ, которая связывает клиентов и строки заказов.Когда я думаю о сущности заказа, более естественно представить ее как не определяемую без идентификатора.Заказ без идентификатора, по-видимому, лучше представить как запрос заказа, чем как заказ.
Чтобы добавить заказ в репозиторий, я обычно вижу, как люди создают экземпляр заказа без идентификатора, а затем репозиторий завершает объект:
class OrderRepository
{
void Add(Order order)
{
// Insert order into db and populate Id of new order
}
}
Что мне нравится в этом подходе, так это то, что вы добавляете экземпляр Order в OrderRepository.Это имеет большой смысл.Однако экземпляр заказа не имеет идентификатора, и в рамках потребителя репозитория для меня все еще не имеет смысла, что заказ не имеет идентификатора.Я мог бы определить OrderRequest как экземпляр порядка и добавить его в репозиторий, но это похоже на получение яблока из апельсина и последующее добавление его в список апельсинов.
Альтернативно, я также видел такой подход:
class OrderRepository
{
Order AddOrder(Customer customer)
// It might be better to call this CreateOrder
{
// Insert record into db and return a new instance of Order
}
}
Что мне нравится в этом подходе, так это то, что заказ не определен без идентификатора.Репозиторий может создать запись базы данных и собрать все необходимые поля перед созданием и возвратом экземпляра заказа.Что здесь пахнет, так это то, что вы никогда на самом деле не добавляете экземпляр заказа в репозиторий.
В любом случае работает, поэтому мой вопрос:Должен ли я жить с одной из этих двух интерпретаций или существует лучшая практика моделирования вставки?
Я нашел этот ответ, который похож, но для объектов значений:как мне добавить объект в коллекцию, поддерживаемую совокупным корнем.Когда дело доходит до объекта значения, путаницы нет, но мой вопрос касается объекта, личность которого получена из внешнего источника (автоматически генерируемый идентификатор базы данных).
Решение
Я хотел бы начать с исключения второго подхода.Это не только кажется нелогичным, но и нарушает несколько хороших принципов проектирования, таких как Разделение команд и запросов и Принцип наименьшего сюрприза.
Остальные параметры зависят от логики предметной области.Если Логика предметной области диктует, что Порядок без идентификатора бессмысленен, идентификатор является обязательным инвариантом Порядка, и мы должны смоделировать его следующим образом:
public class Order
{
private readonly int id;
public Order(int id)
{
// consider a Guard Clause here if you have constraints on the ID
this.id = id;
}
}
Обратите внимание, что, отметив id
поле как readonly
мы сделали его инвариантом.Мы не можем изменить его для данного экземпляра Order.Это идеально сочетается с Доменно-ориентированный дизайн's Сущность шаблон.
Вы можете дополнительно усилить логику домена, поместив в конструктор предложение Guard, чтобы идентификатор не был отрицательным или нулевым.
К настоящему моменту вам, вероятно, интересно, как это будет работать с автоматически сгенерированными идентификаторами из базы данных.Ну, это не так.
Не существует хорошего способа убедиться, что предоставленный идентификатор еще не используется.
Это оставляет вам два варианта:
- Измените идентификатор на Guid.Это позволяет любому вызывающему абоненту предоставить уникальный идентификатор для нового заказа.Однако для этого необходимо также использовать Guids в качестве ключей базы данных.
- Измените API так, чтобы для создания нового заказа требовался не объект Order, а OrderRequest, как вы предложили - OrderRequest может быть почти идентичен классу Order, за вычетом идентификатора.
Во многих случаях создание нового заказа — это бизнес-операция, которая в любом случае требует специфического моделирования, поэтому я не вижу проблем с проведением такого различия.Хотя Order и OrderRequest могут быть очень похожи семантически, они даже не обязательно должны быть связаны в иерархии типов.
Я мог бы даже пойти еще дальше и сказать, что они не должна быть связаны, потому что OrderRequest является Объект значения тогда как Порядок – это Сущность.
Если выбран этот подход, метод AddOrder должен возвращать экземпляр Order (или, по крайней мере, идентификатор), поскольку в противном случае мы не сможем узнать идентификатор заказа, который мы только что создали.Это возвращает нас к нарушению CQS, поэтому я склонен предпочитаю руководства для идентификаторов объектов.