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

从概念上讲,我认为我得到了聚合根,直到我去定义一个聚合根。

我有一个 Employee 实体,它已作为聚合根出现。在商业领域, 一些 员工可以记录与工作相关的违规行为:

员工-----*违规行为

由于并非所有员工都受到此限制,我认为违规行为不会成为员工总数的一部分,对吗?

因此,当我想要处理员工及其相关违规行为时,这是某些服务的两个单独的存储库交互吗?

最后,当我添加违规时,该方法是在员工实体上吗?谢谢您的帮助!

有帮助吗?

解决方案

在做了更多的研究后,我想我已经回答了我的问题。

Paul Stovell对 DDD留言板<上的类似问题进行了略微编辑的回复/ A>。替换<!>“客户<!>”; for <!> quot; Employee <!> quot;和<!> quot; Order <!> quot; for <!> quot; Violation <!> quot;你明白了。

  

仅仅因为客户引用订单   并不一定意味着订单下降   在Customer聚合根目录中。   客户的地址可能是,但是   订单可以是独立的(for   例如,您可能有一个服务   处理所有新订单,无论谁   顾客是。不得不去   客户 - <!> gt;订单没有任何意义   这种情况)。

     

从域名的角度来看,你可以   甚至质疑那些人的有效性   参考(客户参考   订单清单)。你多久一次?   实际上需要所有订单   顾客?在一些系统中它   感觉,但在其他人,一个客户   可能会做很多订单。机会是   你想要一个客户之间的订单   日期范围或客户订单   尚未处理的订单或订单   尚未支付的,等等。   你需要所有的场景   他们可能相对不常见。   但是,它更有可能   在处理订单时,您会   想要客户信息。所以   代码,Order.Customer.Name很有用,   但是Customer.Orders[0].LineItem.SKU -   可能不那么有用。当然,   这完全取决于您的业务   域。

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

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

编辑** 我的域名中的皱纹是Violations没有行为。它们基本上是发生事件的记录。尚不确定其含义。

其他提示

Eric Evan在他的书中指出,领域驱动设计:解决软件核心的复杂性

  

AGGREGATE是一组关联对象,我们将其视为以便进行数据更改

这里有两个要点:

  1. 这些对象应被视为<!> quot; unit <!> quot;。
  2. 出于<!>的目的,“数据更改<!>”;
  3. 我相信你的场景,Employee和Violation不一定是一个单元,而在Order和OrderItem的例子中,它们是单个单元的一部分。

    在对聚合边界进行建模时,另一个重要的事情是聚合中是否有任何不变量。不变量是业务规则,应该在<!>“整个<!>”中有效。骨料。例如,对于Order和OrderItem示例,您可能有一个不变量,表明订单的总成本应小于预定义的金额。在这种情况下,只要您想要向Order添加OrderItem,就应该强制执行此不变量以确保您的订单有效。但是,在您的问题中,我看不到您的实体之间存在任何不变量:Employee和Violation。

    如此简短的回答:

    我认为员工和违规都属于2个单独的聚合。这些实体中的每一个也都是它们自己的聚合根。所以你需要2个存储库:EmployeeRepository和ViolationRepository。

    我也相信你应该有从Violation到Employee的单向关联。这样,每个Violation对象都知道它属于谁。但是,如果您想获取特定员工的所有违规列表,那么您可以询问ViolationRepository:

    var list = repository.FindAllViolationsByEmployee(someEmployee);
    

你说你有员工实体和违规行为,而每个违规行为本身并没有任何行为。从我上面读到的内容来看,在我看来,你可能有两个聚合根:

  • 员工
  • EmployeeViolations(称为 EmployeeViolationCard 或 EmployeeViolationRecords)

EmployeeViolations 由相同的员工 ID 标识,并且它包含违规对象的集合。您可以通过这种方式获得员工行为和违规行为,并且不会获得没有行为的违规实体。

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

我普遍同意莫什的观点。但是,请注意从业务角度来看交易的概念。所以我实际上是为了数据更改而使用<!>“; <!>为了交易的目的而指的是<!>“!”

存储库是域模型的视图。在域环境中,这些<!>“视图<!>”;真正支持或代表业务功能或能力 - 交易。例如,员工可能有一个或多个违规,如果是,则是某个时间点的交易的方面。考虑一下您的用例。

场景:<!>;员工犯下违反工作场所的行为。<!>这是一种发生的业务事件(即事务,或更大的,可能是分布式事务的一部分)。实际上可以从多个角度看待受根域影响的域对象,这就是它令人困惑的原因。但要记住的是与业务交易相关的行为,因为您希望业务流程尽可能准确地模拟现实世界。就关系而言,就像在关系数据库中一样,您的概念域模型实际上应该已经表明了这一点(即关联性),这通常可以在任何一个方向上读取:

员工<!> lt; ----提交-------承诺---- <!> gt;违反

因此,对于这个用例,可以说它是一个处理违规的事务,并且root - 或<!> quot; primary <!> quot;实体 - 是违规行为。那么,那将是您将为该特定业务活动或业务流程引用的聚合根。但这并不是说,对于不同的活动或流程,您不能拥有员工聚合根,例如<!>“新员工流程<!>”。如果你小心,循环引用应该没有负面影响,或者能够以多种方式遍历你的域模型。但是,我会警告,对此的管理应该由您的业务领域的控制器部分或您拥有的任何等效物来考虑和处理。

除此之外:根据模式(即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
}

简而言之,让您的业务流程和交易成为指南,让您的业务基础架构在实施或重构流程/活动时自然发展。此外,优于传统黑盒设计的可组合性。当你进入面向服务或云计算时,你会很高兴你做到了。 :)

我想知道结论会是什么?

'违规'成为根实体。 “违规”将由“员工”根实体引用。即违规存储库<!> lt; - <!> gt;员工存储库

但是你被告知将违规行为视为根实体,因为它没有行为。

但'行为'是否有资格成为根实体的标准?我不这么认为。

这里有一个稍微正交的问题来测试理解,回到Order ... OrderItem示例,系统中可能有一个想要直接查看OrderItems的分析模块,即获取特定产品的所有orderItems,或者所有订单大于某个给定值等的项目,确实有很多像这样的用例并且驱动<!> quot; aggregate root <!> quot;我们认为OrderItem本身就是一个不同的聚合根?

这取决于。是否有任何更改/添加/删除违规行为会改变员工的任何部分 - 例如您是否在过去3年内对员工存储违规计数或违规计数?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top