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

C# 领域驱动设计-如何实现始终有效的状态,c#,validation,domain-driven-design,C#,Validation,Domain Driven Design,我有一个带有OrderItems的域模型订单 必须发出命令 有个经理 至少还有一个orderItem 我的订单结构如下 public Order(Manager manager, IList<OrderItem> orderItems) { if(manager == null) throw new ArgumentNullException(nameof(manager)); if(orderItems == null) throw new ArgumentNul

我有一个带有OrderItems的域模型订单

必须发出命令

  • 有个经理
  • 至少还有一个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-itemiddlist)
    {
    Manager=managerRepo.FindById(managerId);
    订单=新订单(经理);
    //这里的订单没有项目,这是否意味着订单处于无效状态?
    foreach(itemIdList中的int itemId)
    order.AddItem(itemRepo.FindById(itemId));
    order.ReadyForPersistence();
    orderepo.Add(订单);
    }
    
    我是否误解了“始终有效状态”


    我如何才能正确地实现“始终有效的状态模型”

    首先,我想说的是,持久性与您所要求的并不相关。问题是:在您试图建模的域中,空订单真的有效吗

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


    顺便说一句,如果空订单有意义,这实际上取决于你所从事的行业。一定要和你的主题专家谈谈,看看空订单是否只是有不同名称的东西。你可能会发现空订单是有效的,它有自己的规则和动作,但它们有另一个名称,它有一组不同的不变量,如果它有,它可以让事情变得简单得多:你可以有一个“订单草稿”作为您的模型的一部分,它将作为一个工厂有机地工作到您的订单中。

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

    虽然订单必须至少包含1个行项目,但购物车可以包含0到n个项目

    一个过渡状态如何?你把实体变成某种可寻址的东西,比如引用。一旦您将实体设置为地址,就可以自由使用不可变对象

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

    这类工作可以用支持协议的语言(Clojure、Swift、Groovy等)或支持联合类型的语言(F#、Elm、Haskell、Reason、OCaml)来完成。使用其中一种允许相同的消息(例如,
    place
    )具有不同的行为。
    place
    功能将在
    ShoppingCart
    上验证它是否拥有转换为
    订单所需的一切


    我并不是说这是你问题的最终解决方案。我只是把它作为处理这类问题的一种方式放在那里。

    实体可以持久化或正在持久化的事实并不会对实体不变量施加额外的义务。真正重要的是实体状态,它应该是有效的(不变量被满足),而不管这个状态在哪里——在持久化存储中还是在内存中。如果根据你的不变量,没有订单项是订单的无效状态,你不应该允许这样的订单存在。谢谢你的时间和答案。“订单草稿”可能有助于我做出正确的订单。
    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);
    }