Я считаю, что с точки зрения DDD, всякий раз, когда у вас возникают подобные проблемы, вы должны сначала спросить себя, правильно ли была разработана ваша сущность .
Если вы говорите, что у товара есть список товаров. Вы говорите, что Предметы являются частью совокупности Продуктов. Это означает, что если вы выполняете изменения данных на Продукте, вы также меняете элементы. В этом случае ваш Продукт и его товары должны соответствовать требованиям транзакций. Это означает, что изменения в том или ином случае должны всегда каскадироваться по всей совокупности Продуктов, и изменения должны быть ATOMIC. Это означает, что если вы изменили имя Продукта и название одного из его Предметов, и если фиксация имени Предмета в базе данных работает, но не удалась для имени Продукта, имя Предмета следует откатить.
Это факт, что Агрегаты должны представлять границы согласованности, а не удобства композиции.
Если в вашем домене не имеет смысла требовать, чтобы изменения в Товарах и изменениях в Продукте были согласованными с точки зрения транзакций, то Продукт не должен содержать ссылку на Предметы.
Вам все еще разрешено моделировать отношения между Продуктом и товарами , у вас просто не должно быть прямой ссылки. Вместо этого вы хотите иметь косвенную ссылку, то есть Product будет иметь список идентификаторов товаров.
Выбор между наличием прямой ссылки и косвенной ссылки должен основываться в первую очередь на вопросе согласованности транзакций. После того, как вы ответили, что, если вам кажется, что вам нужна согласованность транзакций, вы должны дополнительно спросить, может ли это привести к проблемам с масштабируемостью и производительностью.
Если у вас слишком много предметов для слишком большого количества товаров, это может масштабироваться и работать плохо. В этом случае вы должны рассмотреть возможную последовательность. Это когда у вас все еще есть только косвенная ссылка от Продукта на товары, но с помощью какого-либо другого механизма вы гарантируете, что в какой-то момент в будущем (надеюсь, как можно скорее) Продукт и Предметы будут в согласованном состоянии. Примером может быть то, что при изменении остатков Товаров общий баланс Товаров увеличивается, в то время как каждый товар изменяется один за другим, у Товара может быть не совсем правильный Суммарный Баланс, но как только все предметы закончат изменяться, Продукт обновится, чтобы отразить новый Общий баланс и, таким образом, вернуться в согласованное состояние.
Этот последний выбор сложнее сделать, вы должны определить, допустимо ли иметь конечную согласованность, чтобы избежать проблем с масштабируемостью и производительностью, или если цена слишком высока, и вы бы предпочли согласованность транзакций и оперативность с проблемами масштабируемости и производительности.
Теперь, когда у вас есть косвенные ссылки на элементы, как вы выполняете GetMaxItemSmth ()?
В этом случае я считаю, что наилучшим способом является использование шаблона двойной отправки. Вы создаете класс ItemProcessor:
public class ItemProcessor
{
private readonly IItemRepository _itemRepo;
public ItemProcessor(IItemRepository itemRepo)
{
_itemRepo = itemRepo;
}
public Item GetMaxItemSmth(Product product)
{
// Here you are free to implement the logic as performant as possible, or as slowly
// as you want.
// Slow version
//Item maxItem = _itemRepo.GetById(product.Items[0]);
//for(int i = 1; i < product.Items.Length; i++)
//{
// Item item = _itemRepo.GetById(product.Items[i]);
// if(item > maxItem) maxItem = item;
//}
//Fast version
Item maxItem = _itemRepo.GetMaxItemSmth();
return maxItem;
}
}
И это соответствующий интерфейс:
public interface IItemProcessor
{
Item GetMaxItemSmth(Product product);
}
Который будет отвечать за выполнение необходимой вам логики, связанной с работой как с данными о вашем продукте, так и с данными других связанных объектов. Или это может содержать любую сложную логику, охватывающую несколько объектов и не вполне вписывающуюся ни в один объект, скажем, из-за того, что для этого требуются данные, охватывающие несколько объектов.
Чем, к вашему объекту Product вы добавляете:
public class Product
{
private List<string> _items; // indirect reference to the Items Product is associated with
public List<string> Items
{
get
{
return _items;
}
}
public Product(List<string> items)
{
_items = items;
}
public Item GetMaxItemSmth(IItemProcessor itemProcessor)
{
return itemProcessor.GetMaxItemSmth(this);
}
}
Примечание:Если вам нужно только запросить элементы Max и получить обратно значение, а не Entity, вы должны вообще обойти этот метод. Создайте IFinder с GetMaxItemSmth, который возвращает вашу специализированную модель чтения. Хорошо иметь отдельную модель только для запросов и набор классов Finder, которые выполняют специализированные запросы для извлечения такой специализированной модели чтения. Как вы должны помнить, агрегаты существуют только с целью изменения данных. Хранилища работают только с агрегатами. Поэтому, если данные не меняются, не требуется ни Агрегаты, ни Репозитории.