域驱动设计-如何实现始终有效的状态

时间:2019-02-11 13:20:34

标签: c# validation domain-driven-design

我有一个带有OrderItems的域模型Order。

订单必须

  1. 有一位经理
  2. 至少还有一个orderItem。

我的订单结构如下

public Order(Manager manager, IList<OrderItem> orderItems) 
{
    if(manager == null) throw new ArgumentNullException(nameof(manager));
    if(orderItems == null) throw new ArgumentNullException(nameof(orderItems));
    if(orderItems.Count == 0)
        throw new Exception("List must contain at least one item.");
    foreach(var item in orderItems)
        AddItem(item);
    //assign values
    this.manager = manager;
    ...
    ...
}
Manager manager;
IList<OrderItem> orderItems;
...

void AddItem(OrderItem orderItem)
{
   if(orderItem == null) throw new ArgumentNullException(nameof(orderItem));
   if(orderItems.Contains(orderItem))
       throw new Exception("Order Item duplicate");
   orderItems.Add(orderItem);
}

void CreateNewOrder(int managerId, List<int> itemIdList)
{
    Manager manager = managerRepo.FindById(managerId);
    List<OrderItem> itemList =new List<OrderItem>();
    foreach(int itemId in itemIdList)
        itemList.Add(itemRepo.FindById(itemId));

    Order order = new Order(manager, itemList);
    orderRepo.Add(order);
}

我认为它接近持久性模型,而不是领域模型。

如果我像下面这样编码怎么办?

public Order(Manager manager)
{ 
    if(manager == null) throw new ArgumentNullException(nameof(manager));
    this.manager = manager;
    ...
    ...
}

public void AddItem(OrderItem orderItem)
{
   if(orderItem == null) throw new ArgumentNullException(nameof(orderItem));
   if(orderItems.Contains(orderItem))
       throw new Exception("Order Item duplicate");
   orderItems.Add(orderItem);
}

public void ReadyForPersistence()
{
    if(orderItems.Count == 0)
        throw new Exception("Not ready for persistence");
}

void CreateNewOrder(int managerId, List<int> itemIdList)
{
    Manager manager = managerRepo.FindById(managerId);
    Order order = new Order(manager);

    //Here order has zero item, does this mean order is in invalid state?

    foreach(int itemId in itemIdList)
        order.AddItem(itemRepo.FindById(itemId));

    order.ReadyForPersistence(); 
    orderRepo.Add(order);
}

我是否误解了“始终有效的状态”?

如何正确实施“始终有效的状态模型”。

2 个答案:

答案 0 :(得分:1)

首先,我想说的是,持久性与您要问的无关紧要。问题确实是:空订单确实在您要建模的域中有效吗?

如果空订单在您的域中是错误的或没有任何意义,我会说立即继续并在代码中强制执行此不变式。不允许任何方法完成,以免使订单处于不一致状态。应用程序级代码(在这种情况下为CreateNewOrder方法)的末尾具有有效顺序并不重要。另一个实现者可能会犯一个错误,而忘记在其中添加项目。在这种情况下,您的订单将不会强制执行所需的不变式。

请注意,是否有空订单确实取决于您所从事的行业。 请与您的主题专家交谈,以检查空订单是否只是名称不同的东西。您可能会发现空订单是有效的,它具有自己的规则和操作,但是它们有另一个名称,并且具有一组不同的不变式,如果这样做,它会使您的工作变得简单得多:作为模型一部分的“订单草稿”,将作为订单的工厂有机地工作。

答案 1 :(得分:0)

处理使实体始终有效的设计难题的一种方法是识别状态机的存在。实体在其整个生命周期中不限于保留单个类型。实体是唯一可识别的事物,有时可以在类型之间转换(变形)。

虽然一个订单中必须包含至少1个订单项,但购物车可以包含0到n个商品。

一个过渡状态如何?好吧,您使实体成为某种可寻址的事物,例如引用。将实体设为地址后,就可以自由使用不可变的对象。

一个不变的对象只是一个持久的数据结构,一旦构造就无法使其状态发生变化。相反,您可以对其执行函数/方法,以返回原始对象的修改后的副本,并应用一些有效的更改。这意味着调用任何给定的函数可能会返回相同类型(带有新数据)或新类型(例如状态机转换)。考虑到这一点,您可以在工作流下游的某个地方从ShoppingCartOrder进行实体转换。

可以用支持协议的语言(Clojure,Swift,Groovy等)或支持联合类型的语言(F#,Elm,Haskell,Reason,OCaml)完成这种事情。使用任何一个都允许相同的消息(例如place)具有不同的行为。 place上的ShoppingCart函数将验证它具有转换为Order所需的一切。

我并不是说这是解决您问题的最终方法。我只是把它作为处理这种问题的一种方式。