【问题标题】:delete of entities in domain driven design删除领域驱动设计中的实体
【发布时间】:2015-04-22 16:40:29
【问题描述】:

我最近开始探索领域驱动设计的概念。我发现的大多数示例和解释都假设有一种 ORM 工具用于将实体持久保存在存储库中。

目前,我倾向于使用实体框架作为 ORM 工具,但是我想到了一些可能也适用于其他工具的问题。例如,假设有一个根聚合 Order,其中有多个 OrderLine 实体作为子实体:

  • Order 具有公开 OrderLines 的只读集合属性
  • 订单行的增删是通过订单上的具体方法实现的
  • 添加或删除订单时,订单总价需要相应更改

    1. 在保存订单时检测添加新 OrderLine 的首选方法是什么?在存储库中,我可以检查 id 的值以检测是否需要添加或更新一行。这是正确的方法吗?

    2. 订单库如何检测行删除?

    3. 当某些属性只能私有设置时,如何从数据库中恢复实体?

当在根聚合上公开一个公共的可修改集合时,这些示例很容易实现,但这使得可以在不使用专用方法的情况下修改集合。使用私有属性的恢复也是如此:将它们设为可设置的公共属性。

如果我不想使用 ORM 工具,而是直接使用 SQL 查询,那么在这种情况下如何最好地解决这些问题?理想情况下,在这种情况下,域模型不需要修改,因为持久化更改的方式是存储库的实现细节,因此可以是 EF、NHibernate 或普通的旧 SQL。

【问题讨论】:

  • 这个问题对于 Stack Overflow 来说太宽泛了。 SO 用于回答特定的技术问题。这个问题只会引发争论。
  • 大多数 ORM 能够通过工作单元自动检测这些更改。您基本上必须自己实施 UoW。

标签: c# entity-framework domain-driven-design


【解决方案1】:

我建议将事件溯源作为一种持久化数据的方式。

事件溯源是一种通过存储确定应用程序当前状态的历史记录来保持应用程序状态的方法。

与 DDD 结合使用效果非常好。

https://msdn.microsoft.com/en-us/library/jj591559.aspx#sec1

在您描述的场景中,您将存储类似 OrderLineItemAddedEvent 或 OrderLineItemDeletedEvent 的内容(每个项目信息通常都存储为 JSON blob),然后当您想要重新加载聚合时,您只需重播命令它们被持久化以将您的对象返回到正确的状态。

【讨论】:

    【解决方案2】:

    领域驱动设计是以程序员、项目经理和企业主都能理解的方式对问题进行建模...http://martinfowler.com/bliki/UbiquitousLanguage.html

    您会发现问题所有者不使用“从购物车中删除”一词,他们会使用“从购物车中删除商品”或“将商品添加到购物车”一词。

    总变量的检测和计算由聚合根实体(购物车)管理,其中包含子实体(产品)。 http://dddcommunity.org/library/vernon_2011/

    对于 DDD 项目,不要从担心如何存储数据开始解决问题,而是从担心代码(实体)如何建模正在解决的问题开始。

    由于人们在解释问题时会根据事件来说话,“用户将商品添加到购物车,所以总数应该更新”,因此将问题建模为在事件发生时做出反应并应用更新的事物通常是有意义的.

    这就是事件溯源真正大放异彩的地方,因为实体代码和存储之间不再存在不匹配(有时可能来自使用 ORM 工具),因为所有内容(问题和代码)都以事件的母语解释。

    通过存储每个事件,您可以重播所有事件并逐步重建当前状态。

    每个存储的事件都是不可变的,一旦创建就无法更改,这将通过构造函数中的参数设置只读字段来完成:

    // in UserAddedItemToCart.cs
    public class UserAddedItemToCart
    {
        public UserAddedItemToCart(int productId)
        {
            this.ProductId = productId;
        }
    
        public readonly int ProductId;  
    }
    
    // in UserRemovedItemFromCart.cs
    public class UserRemovedItemFromCart
    {
        public UserAddedItemToCart(int productId)
        {
            this.ProductId = productId;
        }
    
        public readonly int ProductId;
    }
    
    // in Cart.cs
    public class Cart : AggregateBase
    {
        private readonly HashSet<int> Items = new HashSet<int>();
    
        public void AddItem(int itemId)
        {
            RaiseEvent(new UserAddedItemToCart(itemId));
        }
    
        public void RemoveItem(int itemId)
        {
            RaiseEvent(new UserRemovedItemFromCart(itemId));
        }
    
        // wired up via base class
        protected void Apply(UserAddedItemToCart evnt)
        {
            this.Items.Add(evnt.ProductId);
        }
    
        // wired up via base class
        protected void Apply(UserRemovedItemFromCart evnt)
        {
            this.Items.Remove(evnt.ProductId);
        }
    
        public int[] ItemsInCart
        {
            get { return this.Items.ToArray(); }
        }
    }
    

    当我遇到与您相同的问题时,我发现一些链接很有用,并且从删除事物的想法转变为考虑发生的事件:

    https://geteventstore.com/ https://geteventstore.com/blog/20130220/getting-started-part-2-implementing-the-commondomain-repository-interface/ https://github.com/NEventStore/CommonDomain

    您可以查看 AggregateBase 以查看它如何存储事件,以及上面的 implementation-the-commondomain-repository-interface 链接以查看这两个链接如何在没有公共设置器的情况下重建所有内容(您仍然应该为您的代码建模,以便一切都受到聚合根的保护)。

    不要过于关注 ORM 如何存储您的数据。如果通过 CRUD 访问和控制创建、读取、更新、删除更有意义,那么只需以这种方式建模问题。

    带有事件溯源的 DDD 可能会增加更多的复杂性,因此请负责任地使用,并且仅当它可以在减少时间为您的项目增加价值以便以后添加功能的情况下使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-26
      • 2011-08-01
      • 1970-01-01
      • 2011-01-30
      • 2011-01-05
      • 2010-10-05
      • 2013-12-07
      • 1970-01-01
      相关资源
      最近更新 更多