获取域实体的其他数据

时间:2009-08-07 02:10:58

标签: domain-driven-design aggregates

我有一个域聚合,称之为“订单”,其中包含OrderLines列表。订单会跟踪订单行上的金额总和。客户有一个正在运行的“信用”余额,他们可以从中订购,通过汇总其数据库事务的历史记录来计算。一旦他们用完“池”中的所有钱,他们就不能再订购任何产品了。

因此,每次在订单中添加一行时,我需要检查池中剩余的数量,以及订单是否将它们推到了它上面。池中的金额不断变化,因为其他相关客户不断使用它。

问题是,考虑到DDD,我如何获得这个数量,因为我不想用DataContext问题污染我的域层(在这里使用L2S)。由于我不能只从域中查询数据库,我如何获取该数据以便验证业务规则?

这是使用域事件的实例吗?

3 个答案:

答案 0 :(得分:5)

您的订单汇总应完全封装。因此,它需要能够确定添加项目是否有效,即是否超出客户信用。有多种方法可以做到这一点,但它们都依赖于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 );
  }
}

接下来,实现IAddItemsToOrder的Order类需要一个客户实体,以便它可以检查贷方余额。因此,您只需通过定义特定接口来级联相同的技术。订单存储库可以调用客户存储库以返回履行该角色的客户实体,并将其添加到订单聚合中。

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

答案 1 :(得分:2)

在这种情况下,我使用事件或代理卸载责任。也许向您展示的最简单方法是使用一些代码。

您的订单类将有一个Predicate<T>,用于确定客户的信用额度是否足以处理订单行。

public class Order
{
    public Predicate<decimal> CanAddOrderLine;

    // more Order class stuff here...

    public void AddOrderLine(OrderLine orderLine)
    {
        if (CanAddOrderLine(orderLine.Amount))
        {
            OrderLines.Add(orderLine);
            Console.WriteLine("Added {0}", orderLine.Amount);
        }
        else
        {
            Console.WriteLine(
                "Cannot add order.  Customer credit line too small.");
        }
    }
}

您可能会有一个CustomerService类或类似的东西来提取可用的信用额度。在添加任何订单行之前设置CanAddOrderLine谓词。这将在每次添加一行时检查客户的信用。

// App code.
var customerService = new CustomerService();
var customer = new Customer();
var order = new Order();
order.CanAddOrderLine = 
    amount => customerService.GetAvailableCredit(customer) >= amount;

order.AddOrderLine(new OrderLine { Amount = 5m });
customerService.DecrementCredit(5m);

毫无疑问,你的真实场景会比这更复杂。您可能还想查看Func<T>代表。如果客户在订单中超过其信用额度,则委托或事件可用于在下达订单行后减少信用额度或触发某些功能。

祝你好运!

答案 2 :(得分:1)

除了获取“池”值的问题(我将使用OrderRepository上的方法查询值),您是否考虑过此问题的锁定含义

如果“游泳池”不断变化,是否有人在您的规则通过之后,但在您将更改提交到数据库之前,是否有机会进行事务处理?

Eric Evans在他的书(“聚合”)的第6章中提到了这个问题。