【问题标题】:DDD Relate Aggregates in a long process runningDDD Relate Aggregates 在一个长时间运行的进程中
【发布时间】:2020-10-07 01:59:47
【问题描述】:

我正在处理一个项目,我们在其中定义了两个聚合:“项目”和“任务”。除了其他属性之外,该项目还具有点属性。这些点按用户定义的方式分配给任务。在用例中,用户为某些任务分配积分,但项目必须有这些积分可用。 我们目前对此建模如下:

  1. “task.RequestPoints(points)”,此方法将创建具有属性点和 taskId 的聚合 PointsAssignment,在其构造函数中发出 PointsAssignmentRequested 域事件。
  2. 下发事件的处理程序会获取与任务相关的项目和聚合PointsAssigment并调用方法“project.assignPoints(pointsAssigment, service)”,即会将PointAssignment聚合作为参数和服务传递计算任务的当前点与所需点之间的差异。 如果积分可用,项目将修改其积分属性并发出“ProjectPointsAssigned”域事件,该事件将包含 pointsAssignmentId 属性(以及其他属性)
  3. 最后一个事件的处理程序将获取 PointsAssingment 并确认“pointsAssigment.Confirm ()”,此聚合将发出 PointsAssigmentConfirmed 域事件
  4. 最后一个事件的处理程序将调出关联的任务并调用“task.AssignPoints (pointsAssignment.points)”

我的问题是:在第 2 步中传递项目方法中的聚合 PointsAssignment 是否正确?这是我发现能够关联聚合的唯一方法。 注意:我们创建了 PointsAssignment 聚合,以便在失败的情况下我可以保存错误“pointsAssignment.Reject(reasonText)”并将其显示给用户,因为我使用的是最终一致性(每个事务 1 个聚合)。

我们考虑使用流程管理器 (PointsAssingmentProcess),但同样我们需要第三个聚合 PointsAssingment 来关联此流程。

【问题讨论】:

  • 您真的需要可扩展性吗?我的意思是,你能在一个 TX 中改变两个 AR 吗?如果它是具有单个服务器的中型应用程序,您并不总是需要经历最终一致性和消息传递的麻烦。
  • 另外,你能不能把它简化为project.assignPoints(taskId, amount)PointsAssignedToTask {taskId, amount}task.assignPoints(event.amount)?我唯一不喜欢的是不清楚你不能直接打电话给task.assignPoints(…)。也许重命名为task.acknowledgePointsAssigned(),甚至只是task.apply(event)
  • 你看我的评论了吗?
  • 我会研究 sagas。
  • @plalx 在分配积分的事务中,在任务中更改了另一个属性时代,例如,名称,描述等......它是用户更改积分值的形式,名称,描述和提交

标签: domain-driven-design saga eventual-consistency domain-events


【解决方案1】:

我会做一些不同的事情(这并不意味着更正确)。 您的项目不需要了解有关 PointsAssignment 的任何信息。

如果您的项目是具有可用点数的项目,则可以使用简单的方法来删除或添加点数。

  • RemovePointsCommand -> 项目->removePoints(points)
  • AddPointsCommand -> 项目->addPoints(points)

然后,您将有一个 eventHandler 对 PointsAssignmentRequested 做出反应(我想这个人有项目的 id 和点数,也许还有你所说的状态字段)

这个 eventHandler 只会做:

on(PointsAssignmentRequested) -> 调度命令 (RemovePointsCommand)

// 注意,这里最好让客户端为这个操作发送一个ID,所以它可以异步执行。

该命令可以成功也可以失败,并且它们都可以调度事件:

  • 删除点成功
  • 删除点失败 // 请记住,您有一个之前持久化的相关 id

然后,您将拥有一个最终的 eventHandler :

  1. on(RemovePointsSucceeded) -> PointsAssignment.succeed() // 分派 PointsAssignmentSuceded

  2. on(PointsAssignmentSuceeded) -> task.AssignPoints (pointsAssignment.points)

失败的一面

  1. on(RemovePointsFailed) -> PointsAssignment.fail() // 调度 PointsAssignmentFailed

这样您不必将聚合混合在一起,它们只知道彼此的 id,它们可以在不了解其他聚合架构的情况下工作,避免不必要的耦合。

我认为这个问题的语义与银行转账完全一样。

您有银行账户(项目) 您在此银行账户中有钱(积分) 您正在通过转账流程 (pointsAssignment) 转账 您正在向帐户转帐(任务)

银行账户只需要最少的提款和存款操作,不需要了解转账过程。

转账过程需要知道从哪家银行取款以及存入哪个账户。

我想你的 PointsAssignment 是这样的

{
  "projectId":"X",
  "taskId":"Y",
  "points" : 10,
  "status" : ["issued", "succeeded", "failed"]
}

【讨论】:

  • 但如果我调用 project.Remove(points),PointsAssingment 将如何知道这些点已被删除?我需要传递 id,例如 project.Remove(points, pointsAssigmentId),因此项目将发出域事件 ProjectPointsRemoved { points, pointsAssignmentId }
猜你喜欢
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-23
  • 1970-01-01
  • 2018-06-03
  • 2016-01-03
相关资源
最近更新 更多