【问题标题】:DDD how to model time tracking?DDD如何进行模型时间跟踪?
【发布时间】:2017-06-28 21:38:07
【问题描述】:

我正在开发一个具有员工时间跟踪模块的应用程序。当员工开始工作时(例如在某个抽象机器上),我们需要保存有关他工作的信息。每天都有很多员工在很多机器上工作,他们在它们之间切换。当他们开始工作时,他们会通知系统他们已经开始工作。当他们完成工作时 - 他们也会通知系统。

我有一个聚合 Machine 和一个聚合 Employee。这两个是具有自己行为的聚合根。现在我需要一种方法来为任何给定的Employee 或任何给定的Machine 在任何给定的时间段内构建报告。例如,我想查看哪些 machines 确实让 employee 使用了一段时间以及使用了多长时间。或者我想看看哪些员工在这个给定的机器工作了多长时间。

理想情况下(我认为)我的聚合 Machine 应该有方法 startWorking(Employee employee)finishWorking(Employee employee)

我创建了另一个聚合:EmployeeWorkTime,它存储有关 MachineEmployeestart,finish 时间戳的信息。现在我需要一种方法来修改一个聚合并同时创建另一个(或者最好是另一种方法,因为这种方式有点困难)。

此外,员工有一个Shift,描述了他们每天必须工作多少小时。来自Shift 的信息应保存在EmployeeWorkTime 聚合中,以便在Shift 已更改为给定Employee 的情况下保持一致。

改写的问题

我有一个Machine,我有一个Employee。我怎么能保存信息:

这个 Employee 从 1.05.2017 15:00 到 1.05.1017 18:31 在这个 Machine 工作。

我可以简单地使用 CRUD 来做到这一点,在一个事务中保存多个聚合,数据库优先。但我想使用 DDD 方法来管理复杂性,因为整个领域非常复杂。

【问题讨论】:

  • 如果事件溯源在那里适用,我如何有效地存储域事件以更快地构建报告?每个重要的领域事件都应该是它自己的聚合吗?我也不需要从事件中恢复聚合,因为这些事件对于聚合构造毫无用处。
  • 聚合必须保护哪些不变量?
  • 多名员工不能同时使用同一台机器。同一员工不能同时使用多台机器。在另一台机器上开始另一台机器之前,员工必须通知他在给定机器上完成了他的工作。
  • @EwanCoder 我认为这些不变量是肤浅的。您可能会遇到类似“员工 A 开始在机器上工作并去吃午饭但没有表明他已经完成。员工 B 无法使用机器,因为它被员工 A 锁定”这样的情况。我只允许他们记录他们想要的任何内容,然后让他们修复不一致的时间表。您还可以更聪明一点,以便特定员工在开始使用新机器之前必须解决自我矛盾。
  • 例如,员工 B 无论如何都可以使用这台机器,但是当 A 回来尝试使用另一台机器时,系统会询问他何时完成前一台机器的工作。输入的时间(手动)必须在员工 B 开始在机器上工作之前。您可以做的其他事情是在几秒/分钟未使用的机器上设置自动完成计时器。

标签: domain-driven-design


【解决方案1】:

根据我对您的领域的了解,您必须为员工在机器上工作的过程建模。您可以使用流程管理器/Saga 来实现这一点。让我们将其命名为EmployeeWorkingOnAMachineSaga。它是这样工作的(使用 CQRS,您可以适应其他架构):

  • 当员工想要开始在机器上工作时,EmployeeAggregate 会收到命令 StartWorkingOnAMachine
  • EmployeeAggregate 检查员工没有在另一台机器上工作,如果没有,则提出EmployeeWantsToWorkOnAMachine 并将员工的状态更改为wantingToWorkOnAMachine
  • 此事件被EmployeeWorkingOnAMachineSaga 捕获,它从存储库加载MachineAggregate 并发送命令TryToUseThisMachine;如果机器不是空的,那么它会拒绝该命令,并且 saga 将RejectWorkingOnTheMachine 命令发送到EmployeeAggregate,这反过来会改变它的内部状态(当然是通过引发一个事件)
  • 如果机器是空闲的,它会将其内部状态更改为occupiedByAnEmployee(通过引发事件)
  • 当工人停止在机器上工作时类似。

现在我需要一种方法来为任何给定的员工或任何给定的机器在任何给定的时间段内构建报​​告。例如,我想查看员工在一段时间内使用了哪些机器以及使用了多长时间。或者我想看看哪些员工在这台给定的机器上工作了多长时间。

这应该由read-models 实现,他们只监听相关事件并构建您需要的报告。

此外,员工有一个班次,用于描述他们每天必须工作多少小时。来自 Shift 的信息应保存在 EmployeeWorkTime 聚合中,以便在给定员工的 Shift 已更改的情况下保持一致

根据您希望系统的行为方式,您可以使用 Saga 来实现它(如果您希望系统在员工工作更多或更少的情况下执行某项操作)或作为读取模型/报告(如果您只是希望看到不符合日常轮班的员工。

【讨论】:

  • 我对 Shift 的意思是它可以改变(开始/结束时间),然后所有以前的事件将变得不一致,因为它们仅对上一个班次是正确的。
  • 我还创建了 TimeSheet 聚合,它将 EmployeeStartedWorkingAtTheMachine 和 EmployeeFinishedWorkingAtTheMachine 事件持久保存到事件存储中,所有这些都在单个聚合根中整洁而舒适,并且没有任何最终的一致性地狱。虽然我还没有实现不变量验证,但我计划在创建/更新 TimeSheet 状态之前从一些域服务中检查机器和员工聚合。
  • 并非如此。事件不能变得不一致。它们代表了发生的事实。如果一个班次说工人应该从 08:00 开始,而工人从 08:00 开始,然后在一天中的晚些时候,您将工作时间更改为从 09:00 开始,这意味着工人应该及时返回?没有
  • 设计聚合是为了保护不变量,而不是使报告更容易。
  • 另外,尝试对聚合和 sagas 建模,使其尽可能接近真实世界。想象一下,你没有电脑,只有纸质登记;例如,每台机器上都会附有一张纸/注册表,员工每次开始或停止在该机器上工作时都会输入自己的信息 - 这就是 MachineAggregate。
【解决方案2】:

我正在开发一个具有员工时间跟踪模块的应用程序。当员工开始工作时(例如在某个抽象机器上),我们需要保存有关他工作的信息。每天都有很多员工在很多机器上工作,他们在它们之间切换。当他们开始工作时,他们会通知系统他们已经开始工作。当他们完成工作时 - 他们也会通知系统。

这里要注意的一个关键点是,您正在跟踪的活动是在现实世界中发生的。您的模型不是记录簿;世界是。

EmployeeMachine 是真实世界的事物,因此它们可能不是聚合。 TimeSheetServiceLog 可能是;这些是您通过观察现实世界中的活动而构建的聚合(文档)。

如果事件溯源适用于那里,我如何有效地存储域事件以更快地构建报告?每个重要的领域事件都应该是它自己的聚合吗?

基本上,是的——您的事件流将是您观察到的活动。从技术上讲,您可以将其称为聚合,但它非常贫乏。更容易将其视为数据库或日志。

在这种情况下,它可能只是充满了类似的事件

TaskStarted {badgeId, machineId, time}
TaskFinished {badgeId, machineId, time}

记录这些事件后,您将它们转发到域模型。例如,您将使用 Bob 的 badgeId 获取所有事件并将它们发送到他的时间表,该时间表开始尝试计算他在每个工作站的时间。

鉴于 Machine 和 Employee 是聚合根(它们在复杂的相互关系网络中有自己的不变量和业务逻辑,timeshift-feature 只是模块之一)

如果您假设您的数字模型控制着一个真实世界的实体,您可能会陷入困境。 Digital shopping carts and real world shopping carts 不是一回事;当我超出预算时,我手机上运行的域模型无法从我的实体购物车中扔东西。它只能表示,根据它拥有的信息,内容不符合我的预算政策。真相和记录是真实的世界。

Greg Young 在his talk at DDDEU 2016. 中讨论了这个问题

您还可以查看货物 DDD 示例;特别要注意CargoHandlingHistory之间的区别。

聚合是信息资源;它们是具有内部一致性规则的文档。

【讨论】:

  • 谁应该将事件写入数据库?域模型中应该有一些具有相关方法的聚合(例如 machine.UseBy(employee) 或 employee.Use(machine))。鉴于 MachineEmployee 聚合根(它们在复杂的相互关系网络中有自己的不变量和业务逻辑,timeshift-feature 只是模块之一)
  • @EwanCoder 员工是否手动跟踪他们何时开始工作和何时结束?如果是这种情况,那么事件不一致的风险很高(例如缺少完成事件),我认为您的模型不应该尝试强制一致性。最好只记录您被告知的内容,并要求他们稍后手动更正他们的时间表。在这种情况下,我认为您甚至不必关心流版本控制,您可以只附加到不期望任何特定版本的流。
  • 因此,您可能甚至不需要 AR,只需在命令处理程序中生成事件或使用域服务工厂来处理事件。
猜你喜欢
  • 1970-01-01
  • 2022-10-15
  • 2014-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-31
  • 2015-08-10
相关资源
最近更新 更多