这是一个实用的领域驱动设计问题:
从概念上讲,我认为在获得定义之前我会得到Aggregate。
我有一个Employee实体,它已表现为聚合根。在业务中,某些员工可以针对他们记录与工作相关的违规行为:
员工----- *违反
由于并非所有员工都受此限制,我认为Violations不会成为Employee Aggregate的一部分,对吗?
因此,当我想与Employees及其相关的违规行为合作时,某个服务是否有两个独立的Repository交互?
最后,当我添加违规行为时,是员工实体上的那种方法吗? 谢谢你的帮助!
答案 0 :(得分:25)
在做了更多研究之后,我想我已经回答了我的问题。
Paul Stovell对DDD messageboard上的类似问题进行了略微编辑的回复。将“客户”替换为“员工”,将“订单”替换为“违规”,您就明白了。
仅仅因为客户引用订单 并不一定意味着订单下降 在Customer聚合根目录中。 客户的地址可能是,但是 订单可以是独立的(for 例如,您可能有一个服务 处理所有新订单,无论谁 顾客是。不得不去 客户 - >订单没有任何意义 这种情况)。
从域名的角度来看,你可以 甚至质疑那些人的有效性 参考(客户参考 订单清单)。你多久一次? 实际上需要所有订单 顾客?在一些系统中它 感觉,但在其他人,一个客户 可能会做很多订单。机会是 你想要一个客户之间的订单 日期范围或客户订单 尚未处理的订单或订单 尚未支付的,等等。 你需要所有的场景 他们可能相对不常见。 但是,它更有可能 在处理订单时,您会 想要客户信息。所以 代码,
Order.Customer.Name
很有用, 但是Customer.Orders[0].LineItem.SKU
- 可能不那么有用。当然, 这完全取决于您的业务 域。
换句话说,更新客户与更新订单无关。在我的情况下,订单或违规行为可以设想独立于客户/员工处理。
如果违规行有明细行,那么违规和违规行将成为同一集合的一部分,因为更改违规行可能会影响违规行为。
编辑** 我的域名中的皱纹是Violations没有行为。它们基本上是发生事件的记录。尚不确定其含义。
答案 1 :(得分:21)
AGGREGATE是一组关联对象,我们将其视为以便进行数据更改。
这里有两个要点:
我相信你的场景,Employee和Violation不一定是一个单元,而在Order和OrderItem的例子中,它们是单个单元的一部分。
在对聚合边界进行建模时,另一个重要的事情是聚合中是否有任何不变量。不变量是应在“整体”聚合中有效的业务规则。例如,对于Order和OrderItem示例,您可能有一个不变量,表明订单的总成本应小于预定义的金额。在这种情况下,只要您想要向Order添加OrderItem,就应该强制执行此不变量以确保您的订单有效。但是,在您的问题中,我看不到您的实体之间存在任何不变量:Employee和Violation。
如此简短的回答:
我认为员工和违规都属于2个单独的聚合。这些实体中的每一个也都是它们自己的聚合根。所以你需要2个存储库:EmployeeRepository和ViolationRepository。
我也相信你应该有从Violation到Employee的单向关联。这样,每个Violation对象都知道它属于谁。但是,如果您想获取特定员工的所有违规列表,那么您可以询问ViolationRepository:
var list = repository.FindAllViolationsByEmployee(someEmployee);
答案 2 :(得分:2)
您说您有员工实体和违规行为,并且每次违规行为本身都没有任何行为。从我上面的内容可以看出,在我看来,你可能有两个聚合根源:
EmployeeViolations由相同的员工ID标识,并且包含违规对象的集合。您通过这种方式获得员工和违规行为的行为,并且您不会在没有行为的情况下获得违规实体。
违规是实体还是价值对象,您应根据其属性来决定。
答案 3 :(得分:1)
我普遍同意莫什的观点。但是,请注意从业务角度来看交易的概念。因此,我实际上将“出于数据更改的目的”称为“出于交易目的”。
存储库是域模型的视图。在域环境中,这些“视图”实际上支持或表示业务功能或功能 - 事务。例如,员工可能有一个或多个违规,如果是,则是某个时间点的交易的方面。考虑一下您的用例。
情景:“员工犯下违反工作场所的行为。”这是一种发生的业务事件(即事务,或更大的,可能是分布式事务的一部分)。实际上可以从多个角度看待受根域影响的域对象,这就是它令人困惑的原因。但要记住的是与业务交易相关的行为,因为您希望业务流程尽可能准确地模拟现实世界。就关系而言,就像在关系数据库中一样,您的概念域模型实际上应该已经表明了这一点(即关联性),这通常可以在任何一个方向上读取:
员工< ----提交-------由---->提交违反
因此,对于这个用例,可以说它是一个处理违规的事务,并且根(或“主要”实体)是违规。那么,那将是您将为该特定业务活动或业务流程引用的聚合根。但这并不是说,对于不同的活动或流程,您不能拥有Employee聚合根,例如“新员工流程”。如果你小心,循环引用应该没有负面影响,或者能够以多种方式遍历你的域模型。但是,我会警告,对此的管理应该由您的业务领域的控制器部分或您拥有的任何等效物来考虑和处理。
除此之外:根据模式(即MVC)进行思考,存储库是一个视图,域对象是模型,因此应该采用某种形式的控制器模式。通常,控制器声明存储库(聚合根集合)的具体实现和访问。
在数据访问领域......
使用LINQ-To-SQL作为示例,DataContext将是显示Customer和Order实体视图的控制器。该视图是一种非声明性的,面向框架的Table类型(粗略等同于Repository)。请注意,视图会保留对其父控制器的引用,并且通常会通过控制器来控制视图实现的方式/时间。因此,控制器是您的提供者,负责映射,翻译,对象水合等。然后,该模型就是您的数据POCO。几乎是典型的MVC模式。
以N / Hibernate为例,ISession将是控制器通过session.Enumerable(字符串查询)或session.Get(对象id)或session.CreateCriteria(typeof)公开Customer和Order实体的视图。 (客户))。表()
在商业逻辑世界......
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
}
简而言之,让您的业务流程和交易成为指南,让您的业务基础架构在实施或重构流程/活动时自然发展。此外,优于传统黑盒设计的可组合性。当你进入面向服务或云计算时,你会很高兴你做到了。 :)
答案 4 :(得分:0)
我想知道结论会是什么?
'违规'成为根实体。 “违规”将由“员工”根实体引用。即违规存储库&lt; - &gt;员工存储库
但是你被告知将违规行为视为根实体,因为它没有行为。
但'行为'是否有资格成为根实体的标准?我不这么认为。
答案 5 :(得分:0)
这里有一个稍微正交的问题来测试理解,回到Order ... OrderItem示例,系统中可能有一个想要直接查看OrderItems的分析模块,即获取特定产品的所有orderItems,或者所有订单大于某个给定值的项目等,确实有很多类似的用例并将“聚合根”驱动到极端,我们可以说OrderItem本身就是一个不同的聚合根吗?
答案 6 :(得分:0)
这取决于。更改/添加/删除违规行为是否会改变员工的任何部分 - 例如您是否在过去3年内对员工存储违规计数或违规计数?