Как это сделать в DDD без ссылки на репозиторий из объекта домена?

Я изо всех сил пытаюсь найти правильный дизайн, чтобы избежать ссылки на репозиторий из объекта... Допустим, у меня есть классические классы Customer и Order, например:

public class Customer {
    ...
    public IEnumerable<Order> Orders { get; set; }
}

public class Order {
    ...
    public Customer Customer { get; set; }
    public IEnumerable<OrderItem> OrderItems { get; set; }

    public void Submit() {
       ...
    }
}

public class OrderItem {
    public Product Product { get; set; }
    public decimal SellingPrice { get; set; }
}

Теперь предположим, что цена продажи Товара по какой-то причине зависит от того, был ли Товар также куплен (или нет) по предыдущему Заказу, количества товаров в текущем заказе и предыдущем заказе и т. д. Я мог бы сделать так:

public void Submit() {
    Order lastOrder = this.Customer.Orders.LastOrDefault();
    CalculatePrice(lastOrder);

но это загрузит весь список заказов, когда мне действительно нужен только последний!

То, что я хотел бы сделать, это что-то вроде этого:

public void Submit() {
    Order lastOrder = _orderRepository.GetLastOrderFor(Customer);
    CalculatePrice(lastOrder);

Но я понимаю, что ссылка на OrderRepository в домене — это плохой DDD. Итак, что мне делать? Должен ли я помещать Submit() в другое место, кроме домена? Если да, то где вы предлагаете?


person dstj    schedule 26.10.2011    source источник
comment
если это стабильное бизнес-правило, применимое ко всем покупкам, вы можете изменить свою команду как таковую на «Отправить (самый последний заказ)». Или вы также можете перенести алгоритм ценообразования в другой класс и передать его в команду Submit(IPriceCalculator calc)   -  person Marco    schedule 26.10.2011
comment
Если я внедрю алгоритм ценообразования, где будет реализация IPriceCalculator? Мне действительно кажется, что это доменная логика, не так ли?   -  person dstj    schedule 26.10.2011
comment
Это просто служба домена, поэтому она входит в пакет вашего домена.   -  person Marco    schedule 26.10.2011
comment
И эта реализация IPriceCalculator будет иметь ссылку на IOrderRepository... но разве я не должен вместо этого просто использовать доменную службу OrderSubmitter?   -  person dstj    schedule 26.10.2011
comment
отправка заказа звучит как поведение, относящееся к заказу, но только вы знаете свой контекст.   -  person Marco    schedule 26.10.2011
comment
Хорошо, спасибо за ваше объяснение. Поэтому я думаю, что правильно сказать, что ссылка на репозиторий в домене Entity — это плохой дизайн, но можно ссылаться на него в доменной службе ..   -  person dstj    schedule 26.10.2011
comment
правильный. доменные службы необходимы, когда конкретное поведение/процесс не вписывается в существующий класс. вы могли бы обуть его, но вы также заметите увеличение/уменьшение сплоченности и связи.   -  person Marco    schedule 26.10.2011


Ответы (2)


У вас есть две разновидности:

Создайте доменную службу и добавьте от нее зависимость в репозиторий:

public class MyPricingStrategy : IPricingStrategy
{
  public MyPricingStrategy(IOrderRepository repository)
  { ... }
}

таким образом вы создадите репозиторий ссылок на доменную службу, что не так уж и плохо.

Другой вариант — сделать стратегию ценообразования объектом предметной области с передачей прикладным уровнем необходимых данных из репозитория:

public class LastOrderPricingStrategy
{
  public LastOrderPricingStrategy(Order lastOrder)
  { ... }
}

на прикладном уровне

var lastOrder = orderRepository.GetLastOrder(currentCustomer);
var pricingStrategy = new LastOrderPricingStrategy(lastOrder);
person Mohamed Abed    schedule 30.10.2011

Объект Customer может иметь свойство LastOrder:

public class Customer {
    ...
    public IEnumerable<Order> Orders { get; set; }
    public Order LastOrder { get; set; }
}

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

Другой способ, если вы используете ORM, например NHibernate, вы можете создать множественный запрос, чтобы быстро загрузить коллекцию Orders только с последним заказом. то вы можете вернуться к использованию этого:

public void Submit() {
    Order lastOrder = this.Customer.Orders.LastOrDefault();
    CalculatePrice(lastOrder);

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

person Paul T Davies    schedule 30.10.2011
comment
Да, я использую NHibernate. Вы хотите сказать, что я загружаю Eager только последний заказ, а затем Lazy загружаю все заказы, если я получаю доступ к чему-либо, кроме последнего заказа? Как бы я это сделал?! Я думал, что стремительная загрузка заключается в загрузке всех дочерних объектов при загрузке родительского... - person dstj; 31.10.2011
comment
Сначала это озадачило меня, потому что это никогда не всплывало, и мне было интересно, почему этого никогда не было. Я никогда не жадно загружал один объект, а затем нуждался в остальных. Но тогда было очевидно: если мне когда-нибудь понадобились все объекты, я с радостью загрузил их все! Вы бы не загружали только один объект, если они вам понадобятся. - person Paul T Davies; 01.11.2011
comment
Проблема с предложенным вами подходом заключается в том, что если у Клиента есть 5000 заказов, мы загружаем их все, даже если фактически используем только последний (в этом сценарии расчета цены). Это ненужный удар по производительности... - person dstj; 01.11.2011
comment
Я вовсе этого не предлагаю. Я говорю, что если вы собираетесь использовать только один ордер, загружайте только этот ордер. Если вы собираетесь использовать все заказы, загрузите все заказы. - person Paul T Davies; 02.11.2011