Ваша совокупность заказов должна быть полностью инкапсулирована. Следовательно, он должен иметь возможность определить, допустимо ли добавлять товар, то есть превышен ли кредит клиента. Есть разные способы сделать это, но все они зависят от репозитория Order, возвращающего конкретный агрегат, который знает, как это сделать. Это, вероятно, будет отличаться от агрегата заказов, который вы бы использовали, например, для выполнения заказов.
Вы должны распознать, а затем зафиксировать в коде тот факт, что вы ожидаете, что заказ будет выполнять определенную роль в этом случае, то есть роль добавления дополнительных позиций. Вы делаете это, создавая интерфейс для этой роли и соответствующий агрегат, который имеет внутреннюю поддержку этой роли.
Затем ваш уровень обслуживания может запросить у вашего репозитория Order заказ, который удовлетворяет этому явному ролевому интерфейсу, и, таким образом, репозиторий имеет достаточно информации о том, что вам нужно, чтобы иметь возможность построить что-то, что может удовлетворить это требование.
Например:
public interface IOrder
{
IList<LineItem> LineItems { get; }
// ... other core order "stuff"
}
public interface IAddItemsToOrder: IOrder
{
void AddItem( LineItem item );
}
public interface IOrderRepository
{
T Get<T>( int orderId ) where T: IOrder;
}
Теперь ваш служебный код будет выглядеть примерно так:
public class CartService
{
public void AddItemToOrder( int orderId, LineItem item )
{
var order = orderRepository.Get<IAddItemsToOrder>( orderId );
order.AddItem( item );
}
}
Затем вашему классу Order, который реализует IAddItemsToOrder
, нужен объект клиента, чтобы он мог проверить кредитный баланс. Таким образом, вы просто каскадируете ту же технику, определяя конкретный интерфейс. Репозиторий заказов может вызывать репозиторий клиентов, чтобы вернуть объект клиента, который выполняет эту роль, и добавить его в агрегат заказов.
Таким образом, у вас будет базовый ICustomer
интерфейс, а затем явная роль в виде ICustomerCreditBalance
интерфейса, который происходит от него. ICustomerCreditBalance
действует как интерфейс маркера для вашего репозитория клиентов, чтобы сообщить ему, для чего вам нужен клиент, чтобы он мог создать соответствующий объект клиента, и у него есть методы и / или свойства для поддержки конкретной роли. Что-то вроде:
public interface ICustomer
{
string Name { get; }
// core customer stuff
}
public interface ICustomerCreditBalance: ICustomer
{
public decimal CreditBalance { get; }
}
public interface ICustomerRepository
{
T Get<T>( int customerId ) where T: ICustomer;
}
Явные интерфейсы ролей предоставляют репозиториям ключевую информацию, необходимую им для принятия правильного решения о том, какие данные извлекать из базы данных, и следует ли их извлекать быстро или лениво.
Обратите внимание, что в этом случае я поместил свойство CreditBalance
в интерфейс ICustomerCreditBalance
. Однако с таким же успехом он может быть на базовом ICustomer
интерфейсе, а ICustomerCreditBalance
тогда станет пустым интерфейсом «маркера», чтобы репозиторий знал, что вы собираетесь запрашивать кредитный баланс. Все дело в том, чтобы репозиторий знал, какую роль вы хотите получить для объекта, который он возвращает.
Последняя часть, которая объединяет все это, как вы упомянули в своем вопросе, - это события предметной области. Заказ может вызвать событие домена сбоя, если кредитный баланс клиента будет превышен, чтобы уведомить уровень обслуживания о том, что заказ недействителен. С другой стороны, если у клиента достаточно кредита, он может либо обновить баланс объекта клиента, либо инициировать событие домена, чтобы уведомить остальную часть системы о том, что баланс необходимо уменьшить.
Я не добавлял код события домена в класс CartService
, так как этот ответ уже довольно длинный! Если вы хотите узнать больше о том, как это сделать, я предлагаю вам опубликовать еще один вопрос, нацеленный на эту конкретную проблему, и я подробно остановлюсь на нем ;-)
person
Mike Scott
schedule
06.09.2009