代码之家  ›  专栏  ›  技术社区  ›  jlembke

属于聚合根的内容

  •  32
  • jlembke  · 技术社区  · 15 年前

    这是一个实际的领域驱动设计问题:

    从概念上讲,我想在定义一个根之前,我会得到聚合根。

    我有一个雇员实体,它作为聚合根出现。在生意上, 一些 员工可以记录与工作相关的违规行为:

    员工-----*违规

    因为不是所有的员工都要遵守这些规定,我认为违反规定不会成为员工总数的一部分,对吗?

    所以,当我想与员工及其相关的违规行为一起工作时,这两个独立的存储库交互是由某个服务实现的吗?

    最后,当我添加一个冲突时,该方法是针对员工实体的吗? 谢谢你的帮助!

    7 回复  |  直到 9 年前
        1
  •  25
  •   jlembke    15 年前

    在做了更多的研究之后,我想我对我的问题有了答案。

    保罗·斯托维尔对一个类似问题的回答做了一点小小的修改。 DDD messageboard . 用“客户”代替“员工”,用“订单”代替“违规”,你就明白了。

    只是因为客户推荐订单 不一定意味着秩序下降 在客户聚合根目录中。 客户的地址可以,但是 订单可以是独立的(用于 例如,您可能有一个服务 处理所有新订单,无论是谁 客户是。不得不走 客户-订单没有意义 这种情况)。

    从域的角度来看,您可以 甚至质疑它们的有效性 参考(客户参考 订单列表)。你多长时间来一次 实际需要 全部的 订单 顾客?在某些系统中, 感觉,但在其他方面,一个客户 可能会下很多命令。机会是 你想要一个客户的订单 客户的日期范围或订单 尚未处理的,或订单 还没有支付,等等。 你需要的场景 它们中的一个可能比较少见。 然而,更可能的是 处理订单时,您将 需要客户信息。所以在 代码, Order.Customer.Name 是有用的, 但是 Customer.Orders[0].LineItem.SKU - 可能没什么用。当然, 那完全取决于你的业务 领域。

    换句话说,更新客户与更新订单无关。在我看来,可以独立于客户/员工处理订单或违规行为。

    如果违规行为有细线,那么违规行为和违规行为将是同一集合的一部分,因为更改违规行为可能会影响违规行为。

    编辑** 在我的领域,这里的问题是违规行为没有任何行为。它们基本上是发生事件的记录。还不确定它的含义。

        2
  •  21
  •   Kit    13 年前

    埃里克·埃文在他的书中说, Domain-Driven Design: Tackling the Complexity in the Heart of Software ,

    聚合是我们将其视为一个单元的关联对象的集群。 用于数据更改 .

    这里有两个要点:

    1. 这些对象应被视为一个“单元”。
    2. 为了“数据变更”的目的。

    我相信在您的场景中,员工和违规不一定是一个单元,而在订单和订单项的例子中,它们是单个单元的一部分。

    建模aggregate边界时,另一件重要的事情是在聚合中是否有任何不变量。不变量是应该在“整体”聚合中有效的业务规则。例如,对于order和orderitem示例,您可能有一个不变量,该不变量表示订单的总成本应小于预定义的金额。在这种情况下,只要您想向订单添加orderitem,就应该强制执行这个不变量,以确保您的订单是有效的。然而,在您的问题中,我看不到您的实体之间的任何不变量:雇员和违规。

    这么简单的回答:

    我认为员工和违规行为分别属于两个单独的集合。这些实体中的每一个都是它们自己的聚合根。因此,您需要两个存储库:EmployeeRepository和ViolationRepository。

    我也相信你应该有一个从违反到员工的单向联系。这样,每个违规对象都知道它属于谁。但是,如果要获取特定员工的所有违规列表,则可以向违规存储库询问:

    var list = repository.FindAllViolationsByEmployee(someEmployee);
    
        3
  •  2
  •   Yuriy    13 年前

    你说你有员工实体和违规行为,而每一个违规行为本身都没有任何行为。从我上面所读到的,在我看来,你可能有两个总的根源:

    • 员工
    • 员工违规(称为员工违规卡或员工违规记录)

    EmployeeViolations由同一个员工ID标识,它包含一组冲突对象。这样就可以将员工行为和违规行为分开,而没有行为就不会得到违规实体。

    违反是实体还是值对象,您应该根据其属性来决定。

        4
  •  1
  •   Timex    14 年前

    在这一点上,我一般同意莫什的观点。但是,请记住业务观点中的事务概念。所以我实际上把“数据变更的目的”理解为“交易的目的”。

    存储库是域模型的视图。在域环境中,这些“视图”实际上支持或表示业务功能或功能——事务。在这种情况下,员工可能有一个或多个违规行为,如果是,则是某个时间点中事务的各个方面。考虑您的用例。

    场景:“员工的行为违反了工作场所。”这是发生的一种业务事件(即事务,或更大的、可能是分布式事务的一部分)。受根影响的域对象实际上可以从多个角度看到,这就是为什么它令人困惑的原因。但是要记住的是行为,因为它与业务事务相关,因为您希望您的业务流程尽可能精确地模拟真实世界。在关系方面,就像在关系数据库中一样,概念域模型实际上应该已经指出了这一点(即关联性),通常可以从以下两个方向读取:

    员工----犯------由---->违反

    因此,对于这个用例,公平地说,它是一个处理违规的事务,而根或“主要”实体是违规的。那么,这将是您将为特定业务活动或业务流程引用的聚合根。但这并不是说,对于不同的活动或流程,您不能拥有员工聚合根,例如“新员工流程”。如果要小心,就不应该存在循环引用的负面影响,或者能够以多种方式遍历域模型。不过,我会警告您,这方面的管理应该由您的业务域中的控制器部分,或者您拥有的任何等效部分来考虑和处理。

    旁白:从模式(即MVC)的角度考虑,存储库是一个视图,域对象是模型,因此应该使用某种形式的控制器模式。通常,控制器声明存储库(聚合根的集合)的具体实现和访问。

    在数据访问世界中…

    以LinqToSQL为例,DataContext将是控制器,它公开客户和订单实体的视图。该视图是一种非声明性的、面向框架的表类型(大致相当于存储库)。请注意,视图保持对其父控制器的引用,并且经常通过控制器来控制如何/何时实现视图。因此,控制器是您的提供者,负责映射、翻译、对象水合等。模型就是您的数据POCO。几乎是典型的MVC模式。

    以n/hibernate为例,isession将是控制器,通过session.enumerable(字符串查询)或session.get(对象ID)或session.createReciteria(typeof(customer)).list()公开客户和订单实体的视图。

    在商业逻辑世界中…

    Customer { /*...*/ }
    
    Employee { /*...*/ }
    
    Repository<T> : IRepository<T>
                  , IEnumerable<T>
                  //, IQueryable<T>, IQueryProvider //optional
    
    { /**/ }
    
    BusinessController {
     Repository<Customer>  Customers { get{ /*...*/ }} //aggregate root
     Repository<Order> Orders { get{ /*...*/ }} // aggregate root
    }
    

    简而言之,让您的业务流程和事务成为指导,让您的业务基础架构随着流程/活动的实现或重构而自然地发展。此外,与传统的黑盒设计相比,更喜欢可组合性。当你开始面向服务或云计算时,你会很高兴你做到了。:)

        5
  •  0
  •   Sanjay    15 年前

    我想知道结论是什么?

    “冲突”将成为根实体。“员工”根实体将引用“违规”。IE违规存储库<->员工存储库

    但是你被认为是违反根实体,因为它没有任何行为。

    但是“行为”是一个符合根实体资格的标准吗?我不这么认为。

        6
  •  0
  •   redzedi    12 年前

    这是一个测试理解的稍微正交的问题,返回到订单……订单项示例,系统中可能有一个分析模块想要直接查看订单项,即获取特定产品的所有订单项,或者所有订单项大于某个给定值等,确实有很多像这样的用例和驱动。”聚合根”到极端,我们是否可以认为orderitem本身是一个不同的聚合根??

        7
  •  0
  •   Chalky    9 年前

    视情况而定。变更/添加/删除变更是否会改变员工的任何部分-例如,您是否在过去3年内针对员工存储违规计数或违规计数?