一个根应该用于整个聚合图还是多个根应该在同一个图中?

Should one root be for the whole aggregate graph or should multiple roots be in the same graph?

我对设计中以下聚合的结构感到困惑。

一个根应该用于整个聚合图还是多个根应该在同一个图中?

我的情况:

工作时间规定

Id|    Name            |   NumberOfAvailableRotations|  IsActive 
 1|    General Rule    |          2                  |    true 

工作时间

Id|  Name   |   NumberOfHours| NumberOfShortDays |WorkTimeRegulationId 

1 | Winter  |     8          |    1              |    1
2 | Summer  |     6          |    0              |    1

WorkTimeActivation

 Id|  StartDate  | EndDate      | IsDeFacto |WorkTimeId    
 1 |  2018-10-1  | 2018-12-30   |    1      |    1

Note: I consider (StartDate&EndDate) as DateTimeRange(valueobject)


现在我是否应该将 WorktimeRegulation 视为整个图形的根,以便它控制两者 (WorkTime,WorkTimeActivation)?

WorktimeRegulation(ROOT)
        |
        V
    WorkTime
        |
        V
WorkTimeActivation

或者我应该有两个像这样的聚合:

WorktimeRegulation(ROOT)          |        WorkTime(ROOT)  
        |                         |             |
        V                         |             V
    WorkTime                      |      WorkTimeActivation

所以在第二个解决方案中我有两个根!但是,如果我将 WorkTime 视为根,它将可以与 WorktimeRegulation 分开使用,我不希望这样,因为这会破坏第一个聚合的完整性。


或者我应该有两个像这样的聚合:

WorktimeRegulation(ROOT)          |      WorkTimeActivation(ROOT)  
        |                         |             |
        V                         |             V
    WorkTime                      |         WorkTime

基于评论:

Could you just explain the business problem this solves and what invariants must be protected instead of the database structure


  1. WorkTime可以进行哪些行为?到目前为止我可以看到它可以 被激活。

    是的,除了更新WorkTime,还可以激活

  2. 为了验证激活你需要整个 激活历史记录还是仅最近的事件?

    不是整个激活历史,我可以说是最近的单个激活历史。

  3. 激活规则是否跨越多个 WorkTime(例如,如果一个 active 那么另一个不能)?

    Yes Per WorktimeRegulation 我的意思是如果我有 WorktimeRegulation 包含 3 WorkTimes 那么对于此规则只有一个有效的 Worktime

  4. 是否可以修改激活的名称、小时数等?

    如果您的意思是 IsDeFacto 用于 Activation 那么是(通过此属性,如果同一 [=16= 的两次激活之间存在冲突,用户可以强制执行特定激活]), 因为你问题中前面的属性属于WorkTime而不是Activation,所以可以修改numberOfHours.

  5. 修改这些细节是否会与其他业务冲突 操作(例如激活 WorkTime?

    如果您的意思是these details前面的属性,那么答案是否定的。

  6. "如果相同的两次激活之间存在冲突 WorktimeRegulation” 你能详细说明什么样的冲突可以 发生?系统不应该阻止激活冲突吗? 发生?激活过程究竟是如何进行的?

    根据业务专家的解释:这可能会发生冲突,系统不应该阻止它,但会向最终用户警告此冲突。 激活执行如下: 用户在WorkTimeRegulation中选择一个特定的WorkTime来激活,然后激活弹出窗口允许用户插入StartDate和预期的EndDate,当用户点击激活时,它应该检查是否与之前的激活有冲突在同一个 WorkTimeRegulation 中并提醒用户,如果他想要先前激活的优先级,那么他应该使用 IsDefacto 在发生冲突的情况下强制执行其中一个。 注意:终端用户事先并不知道激活工作时间的确切结束日期,所以他插入了预期的结束日期,因此可能会发生冲突。

  7. 让 WorktimeRegulation 指向一个 短时间内不存在的 WorkTime(例如 regulation.activateWorkTime(workTimeId) 然后激活工作 项目被删除)? WorkTime 可以得到 deleted/archived 吗?

    WorktimeRegulation不能指向不存在的WorkTime工作时间至少激活一次不能删除或存档,刚刚激活和(在同一规定中激活其他工作时间时停用)

  8. WorkTime 可以关联到不同的 Regulation 之后吗 创作?

    没有

  9. 你确定这无关紧要,例如,姓名或 numberOfHours 在有人尝试更改的同时更改 激活工作时间?

    现在我明白你的问题了,用户在第一次激活后无法更改工作时间。

  10. 我仍然不清楚为什么会发生激活冲突。 为什么要允许重叠的激活期,但随后 使用一个标志来指定要执行的标志。似乎很容易 而是防止激活期重叠,不是吗?真的吗 从业务角度来看,两个激活期可能 概念上重叠?为什么用户甚至输入 startDate 和 手动结束日期?你不能只跟踪日期 activation/deactivation发生在系统中?

    根据业务专家的说法,特定 WorkTime 的激活开始日期是公司经理做出的决定(未计划)使用此 WorkTime 并在持续时间(无法预测,因为这是一个决定)公司经理决定切换到另一个 WorkTime,因此 HR 员工通过插入特定的开始日期和插入大致的结束日期来执行决定,因此下一次激活可能与最近的一个。

  11. WorkTime 的实际业务含义是什么 activation/deact?

    在这一年中,受特定WorkTimeRegulation约束的员工,他们的WorkTimes根据the activation发生了变化,我的意思是WorkTime可能从10月到12月是8小时,然后从1月到9月换成6小时(另一个WorkTime),如此循环。

Note: The decision provides for start date only (e.g Winter WorkTime Starts from date ...) and not specifying the end date! So it is inserted roughly and as a result the start date for the next activation may conflict with the end date for the previous one)

  1. 是因为他们可能会提前创建激活吗?在那里面 情况下,IMO 是同一件事。强制执行结束日期 冲突激活恰好是主要激活的时间 starts 与拥有实际概念几乎相同,因为 当您使用 deFacto 强制激活另一个 重叠你隐含地声明另一个激活已经结束 当事实上的开始时,不是吗?

    在询问了业务专家后,他是这样解释案例的:HR部门向公司经理发送了一个激活期(开始日期,预计结束日期)的建议,之后manager 确认了建议,HR 执行了建议,他们需要在当前 active worktime 结束日期之前提醒他们将下一个建议发送给公司经理,以便他们可以使用 defacto 强制执行下一个周期或只是激活不重叠 worktime,所以是的,激活提前了时间

13.I 认为我们唯一需要解决的是是否多个 激活建议可以并行进行,规则是什么?

不能为同一个WorkTimeRegulation并行提出多个激活建议,可以为多个WorkTimeRegulation但不能为同一个WorkTimeRegulation

  1. 另外,建议审批流程是否需要建模 在系统中(例如跟踪谁批准了,也许通过上传 批准的电子邮件副本)?

    没有这个过程是手动执行的,不需要建模。

  2. 最后,是否只能激活建议激活 那个被批准了?

第二个选项你错了。工作时间不属于作为子实体的第一个聚合。它只是作为根属于第二个聚合。第一个聚合将引用第二个聚合。你说你不想要它,因为它会破坏完整性。好吧,这完全是关于你想把交易边界放在哪里。如果您想在一次交易中更改三个实体,请寻求第一个意见(一个聚合)。如果您可以忍受延迟,请选择第二个选项(两个聚合),并在它们之间保持最终一致性。

更新:

看来你对聚合的理解是错误的。您的新绘图:

WorktimeRegulation(ROOT)      |      WorkTimeActivation(ROOT)
    |                         |             |
    V                         |             V
WorkTime                      |         WorkTime

也错了。如果 WorkTime 是聚合中的子实体,则另一个聚合必须引用根,而不是子实体。除此之外,WorkTime 是一个实体。您不能将它绘制两次属于两个聚合。一个实体只属于一个聚合。

您有 3 个实体。你有这些聚合的可能性:

  • 一个包含 3 个实体的集合。
  • 两个聚合体:一个有 2 个实体;另一个实体。
  • 三蕴:一蕴一实体。

一些基本规则:

  • 如果聚合引用另一个聚合,它必须引用根,调用根提供的行为。
  • 因此,一个聚合的子实体不能被另一个聚合的实体引用。
  • 聚合的子实体可以引用另一个聚合根。

你必须研究的是,在事务中是否必须修改 3 个实体的状态以保持不变量相同,或者你可以将一个实体拆开,它本身就是另一个聚合。

即使这 3 个实体应该只形成一个聚合以保持不变量相同...您也可以拆分它,但是您必须将两个聚合与事件进行通信才能获得最终的一致性,因为您破坏了事务一致性。

Should one root be for the whole aggregate graph or should multiple roots be in the same graph?

视情况而定。

更重要的一点:它很少依赖于域实体之间的关系结构。

识别聚合边界主要是关于实体状态如何变化;特别是如果任何这些更改需要两个实体之间同时协调。

将实体组合成一个聚合使更改协调变得简单,但有一个成本 - 您不能再对实体进行独立的并发更改。独立的编辑要么需要互相阻塞等待,要么需要重新计算一个编辑。

将实体拆分为单独的聚合使并发编辑更加容易,但代价是实体之间的协调更改变得更加困难。

单个聚合确实希望拥有一个存储位置——试图协调两个不同位置的写入(也称为两阶段提交)是一种拖累。如果两个实体属于不同的聚合,该模型意味着它们可以存储在不同的设备中。

这也表明它们具有独立的生命周期——聚合的优势之一是清楚地了解一起归档的对象图。

要检查的另一件事是您的对象图是多个实体,还是具有描述其状态的复合值的单个记录。如果它可能是单个值,那么建议先尝试单个聚合。

您还可以查看数据来源,尤其是随时间变化的来源。如果更改总是来自相同的权威机构,那么它很可能是一个单一的聚合。另一方面,如果 WorktimeRegulationWorktime 数据来自不同的来源,则更可能发生并发更改,这暗示存在多个聚合。

一个需要探索的重要问题:如果实体不完全一致,企业的成本是多少?如果存储在 Worktime 中的数据有一分钟不符合 WorktimeRegulation,有人会注意到吗?在您可以自由安排事情的情况下,您可以灵活调整更改顺序的情况下,倾向于多个聚合。

Two aggregates the WorkTime is the child in both of them

模型中的每个实体都应该属于一个集合;理论上,您可以在 WorktimeRegulation 中有一个 WorkTime,在 WorktimeActivation 中有一个 WorkTime,但是要让 same 工作时间在 WorktimeRegulation 聚合中 and 在 WorktimeActivation 聚合中是 "against the rules"。这意味着要么对 WorktimeActivation 的更改会影响 WorktimeRegulation,要么 WorkTime 本身应该分成两个独立的部分。

假设以下规则:

  • 在任何给定时间,每个工作时间规定只能有一个有效工作时间。

  • 在任何给定时间只能有一个待处理的工作时间激活建议。

  • 不需要在系统中跟踪建议批准过程。

  • 工作时间规定不能指向不存在的工作时间。工作时间一旦激活就不能删除或归档。

我认为对此进行建模是合理的,因此 WorkTimeRegulation 是一个包含 WorkTime 个实体集合的聚合根。此外,它必须对当前激活建议以及当前活动 WorkTime 进行建模,以执行上述规则。 suggestion/active 状态可以在 WorkTimeRegulation 本身或 WorkTime 实例上建模(在这种情况下,根必须确保一次只有一个处于活动状态,等等)。这确实是当时的设计偏好,两种策略都可以让您保护不变量。此外,WorkTime 实体还应跟踪它是否曾被激活,以防止对其其他属性进行进一步修改(例如 numberOfHours)。

这是它的草稿:

上述模型将确保检查不变量所需的所有数据都是同一个根的一部分,并对存储状态和在内存中执行规则所需的最小结构建模。

但是,仅此模型并不能解决激活历史记录问题。您不需要整个历史来强制执行不变量,因此您不需要在 AR 的边界内对其进行建模,但业务肯定仍然需要此类数据。

此时您可以使用多种策略,例如:

  • 使用事件溯源为您的 AR 建模,免费获取历史记录。
  • 在存储当前 AR 状态的同时调度域事件并将它们存储在事件存储中,仍然允许您预测事件的历史。

  • 为不可变的 ActivationHistoryEntry AR 建模。例如。 historyEntry = regulation.activate(workTimeId); save(historyEntry); save(regulation);。这与领域事件非常相似。

上述模型当然不完美,可以改进。例如,我还考虑过对 Activation VO 的概念进行建模,而不是使用布尔标志,它可以是不同类型(活动的、非活动的、建议的)。这可能会允许一种更具表现力的语言,例如 ActiveActivation 只能通过激活 ActivationSuggestion 获得,其中 ActiveActivation 保留建议的结束日期(如果可用) , 以通知 HR 他们需要发送下一个建议)。