聚合根何时应包含另一个AR(何时不包含)


15

首先让我为这篇文章的长度道歉,但是我真的很想在前面传达尽可能多的细节,所以我不会浪费您的时间在评论中来回走动。

我正在按照DDD方法设计应用程序,并且想知道我可以遵循什么指南来确定聚合根是否应该包含另一个AR,或者是否应该将它们保留为单独的“独立” AR。

以一个简单的“时钟”应用程序为例,该应用程序允许员工在一天之内或每天进行自我计时。用户界面允许他们输入员工ID和PIN,然后对其进行验证并检索员工的当前状态。如果该员工当前正在上班,则UI会显示一个“ Clock Out”按钮。相反,如果它们没有被计时,则按钮显示为“ Clock In”。该按钮采取的操作也对应于员工的状态。

该应用程序是一个Web客户端,它调用通过RESTful服务接口公开的后端服务器。我在创建直观,易读的URL时的第一步是产生了以下两个端点:

http://myhost/employees/{id}/clockin
http://myhost/employees/{id}/clockout

注意:在验证员工ID和PIN并在标头中传递表示“用户”的“令牌”后,才使用它们。这是因为存在一种“经理模式”,允许经理或主管调入或调出另一名员工。但是,为了便于讨论,我试图使其保持简单。

在服务器上,我有一个提供API的ApplicationService。我对ClockIn方法的最初想法是:

public void ClockIn(String id)
{
    var employee = EmployeeRepository.FindById(id);

    if (employee == null) throw SomeException();

    employee.ClockIn();

    EmployeeRepository.Save();
}

这看起来很简单,直到我们意识到员工的时间卡信息实际上是作为交易列表维护的。这意味着每次调用ClockIn或ClockOut时,我不会直接更改Employee的状态,而是将新条目添加到Employee的TimeSheet中。Employee的当前状态(已计时或未计时)是从TimeSheet中的最新条目得出的。

因此,如果我使用上面显示的代码,则我的存储库必须认识到Employee的可持久属性没有更改,但是在Employee的TimeSheet中添加了新条目并执行到数据存储的插入。

另一方面(这是帖子的最终问题),TimeSheet似乎既是聚合根又具有身份(员工ID和期间),我可以轻松地实现与TimeSheet.ClockIn相同的逻辑(员工ID)。

我发现自己正在辩论这两种方法的优点,并且如开篇所述,想知道我应该评估哪种标准以确定哪种方法更适合该问题。


我已经编辑/更新了帖子,所以问题更加清楚,并且使用了更好的方案(希望如此)。
SonOfPirate

Answers:


4

我倾向于实施一项用于时间跟踪的服务:

public interface ITimeSheetTrackingService
{
   void TrackClockIn(Employee employee, Timesheet timesheet);

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

我认为上班或下班不是时间表的责任,也不是员工的责任。


3

聚合根不应相互包含(尽管它们可能包含彼此的ID)。

首先,TimeSheet真的是聚合根吗?它具有身份的事实使它成为一个实体,而不一定是一个AR。查看以下规则之一:

Root Entities have global identity.  Entities inside the boundary have local 
identity, unique only within the Aggregate.

您将TimeSheet的身份定义为员工ID和时间段,这表明TimeSheet是Employee的一部分,时间段为本地身份。由于这是一个Time Clock应用程序,因此Employee成为TimeSheets容器的主要目的是什么?

假设您确实需要使用AR,ClockIn和ClockOut看起来更像是TimeSheet操作,而不是员工操作。您的服务层方法可能如下所示:

public void ClockIn(String employeeId)
{
    var timeSheet = TimeSheetRepository.FindByEmployeeAndTime(employeeId, DateTime.Now);    
    timeSheet.ClockIn();    
    TimeSheetRepository.Save();
}

如果您确实需要同时在Employee和TimeSheet中跟踪计时状态,请查看Domain Events(我不认为它们在Evans的书中,但在线上有很多文章)。它将类似于:Employee.ClockIn()引发EmployeeClockedIn事件,该事件处理程序将拾取该事件,然后调用TimeSheet.LogEmployeeClockIn()。


我明白你的观点。但是,某些规则会约束是否以及何时可以上班。这些规则主要由员工的当前状态决定,例如是否终止,已经上班,计划在当天工作,甚至是上班。当前的班次等。TimeSheet是否应具备此知识?
SonOfPirate 2012年

3

我正在按照DDD方法设计应用程序,并且想知道我可以遵循什么指南来确定聚合根是否应该包含另一个AR,或者是否应该将它们保留为单独的“独立” AR。

聚合根绝对不能包含另一个聚合根。

在您的描述中,您有一个Employee 实体,以及一个Timesheet实体。这两个实体是截然不同的,但可以包括彼此的引用(例如:这是鲍勃的时间表)。

这就是基本建模。

汇总根问题略有不同。如果这两个实体在事务上彼此不同,则可以将它们正确地建模为两个不同的集合。通过事务上的区别,我的意思是没有业务不变性需要同时了解Employee的当前状态和TimeSheet的当前状态。

根据您的描述,Timesheet.clockIn和Timesheet.clockOut无需检查Employee中的任何数据来确定是否允许该命令。因此对于很多问题,两个不同的AR似乎是合理的。

考虑AR边界的另一种方法是询问允许同时进行哪些类型的编辑。是否允许经理在HR摆弄员工个人资料的同时将员工赶出去?

另一方面(这是帖子的最终问题),TimeSheet似乎既是汇总根又具有标识(员工ID和期间)

身份仅表示它是一个实体-是业务不变性决定是否应将其视为单独的聚合根。

但是,某些规则会约束是否以及何时可以上班。这些规则主要由员工的当前状态决定,例如是否终止,已经上班,计划在当天工作,甚至是上班。当前的班次等。TimeSheet是否应具备此知识?

也许-总应该如此。这并不一定意味着时间表应该。

也就是说,如果修改时间表的规则取决于Employee实体的当前状态,则Employee和Timesheet肯定需要属于同一集合,并且整个集合负责确保规则符合跟着。

聚合具有一个根实体;识别它是难题的一部分。如果一个雇员有多个时间表,并且它们都是同一聚合的一部分,那么时间表绝对不是根。这意味着应用程序无法直接修改时间表或将其调度到时间表中-它们需要调度到根对象(可能是Employee),后者可以委派某些职责。

另一个检查是考虑如何创建时间表。如果它们是在员工上班时隐式创建的,那么这又表明它们是聚合中的从属实体。

顺便说一句,您的聚合不太可能具有自己的时间感。相反,应该把时间传递给他们

employee.clockIn(when);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.