您的 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
类中,因为这个答案已经很长了!如果您想了解有关如何执行此操作的更多信息,我建议您发布另一个针对该特定问题的问题,我将在那里进行扩展;-)