Domain driven design 获取域实体的附加数据

Domain driven design 获取域实体的附加数据,domain-driven-design,aggregates,Domain Driven Design,Aggregates,我有一个域聚合,称之为“Order”,它包含医嘱行列表。订单跟踪订单行上的金额总和。客户有一个运行中的“信用”余额,他们可以从中订购,该余额通过对其数据库事务的历史记录求和来计算。一旦他们用完了“池”中的所有钱,他们就不能再订购任何产品了 因此,每次向订单中添加一行时,我都需要检查池中还有多少剩余,以及订单是否将它们推到池上。池中的金额不断变化,因为其他相关客户不断使用它 问题是,从DDD的角度考虑,既然我不想用DataContext问题(这里使用L2S)污染我的域层,那么我如何获得这个数量。既

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

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

问题是,从DDD的角度考虑,既然我不想用DataContext问题(这里使用L2S)污染我的域层,那么我如何获得这个数量。既然我不能仅仅从域中查询到数据库,那么我该如何获取数据以便验证业务规则呢


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

在这种情况下,我使用事件或委托卸载责任。也许最简单的方法就是使用一些代码

您的订单类将有一个
谓词
,用于确定客户的信用额度是否足以处理订单行

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.");
        }
    }
}
毫无疑问,你的真实情况会比这更复杂。您可能还想签出
Func
委托。委托或事件可用于在下单后减少信用额度,或在客户超过订单中的信用额度时启动某些功能


祝你好运

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

如果“池”不断变化,是否有可能在您的规则通过之后,但在您将更改提交给db之前,其他人的事务悄悄进入


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

您的订单聚合应该完全封装。因此,它需要能够确定添加项目是否有效,即是否超过客户信用。有多种方法可以做到这一点,但它们都依赖于订单存储库返回的特定聚合,该聚合知道如何做这一特定的事情。例如,这可能是一个不同于用于满足订单的订单聚合

您必须认识到,然后在代码中捕获这样一个事实,即您希望订单履行特定的角色,即添加额外行项目的角色。为此,您可以为此角色创建一个接口,以及具有该角色内部支持的相应聚合

然后,您的服务层可以向您的订单存储库请求满足此显式角色接口的订单,因此存储库有足够的信息,说明您需要什么来构建能够满足该需求的产品

例如:

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;
}
显式角色接口为存储库提供了所需的关键信息,以便存储库正确决定从数据库中获取哪些数据,以及是主动获取还是被动获取

注意,在本例中,我将
CreditBalance
属性放在
icCustomerCreditbalance
界面上。但是,它也可以位于基本
icCustomer
界面上,然后
icCustomerCreditBalance
变成一个空的“标记”界面,让存储库知道您将要查询信用余额。这一切都是为了让存储库知道您希望它返回的实体扮演什么角色

正如您在问题中提到的,将这一切结合在一起的最后一部分是域事件。如果超过客户的信用余额,订单可以引发故障域事件,以通知服务层订单无效。另一方面,如果客户有足够的信用,它可以更新客户对象上的余额,或者引发域事件来通知系统的其余部分需要减少余额


我没有将域事件代码添加到
CartService
类中,因为这个答案已经很长了!如果您想了解更多关于如何做到这一点的信息,我建议您针对该特定问题发布另一个问题,我将在那里详细介绍;-)

我喜欢这样。我现在做的是将服务注入到域对象中,这样它就不知道从哪里获得了数量。在概念上,这与你们在这里所做的相似。我试试这个,看看我有多喜欢。谢谢凯文!如果对你有用的话没关系。另一种方法是将域模型保持在比域服务更低的层中。它感觉像是一种更好的关注点分离,有助于测试。我真的很喜欢使用杰弗里·巴勒莫的洋葱结构。你应该去看看。域实体应该能够执行这些业务需求,而不需要特别注入接口、谓词等。关键是让您的存储库返回适合当时需要的确切角色的对象。这将完全解放您的服务和应用程序代码,使其不必了解域逻辑,域逻辑完全封装在您的域实体中。因此,您的代码的意图更加清晰,没有一大堆麻烦。@Kevin,域事件(谢谢Udi Dahan)让我摆脱了注入服务的困境。工作得很漂亮。DDD/洋葱架构风格的关注点分离让我对注入服务感到不舒服。@jlembke:Udi是个天才。看起来很合适!你们应该发布你们的解决方案并回答你们自己的问题。迈克,从总体设计的角度来说,这是一个很好的答案。如果我在东北有机会
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 );
  }
}
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;
}