我目前正在考虑域事件的粒度。据我所知,这是一个很好的模式,从命令到域事件的1:1关系开始,以便域事件表达用户所做的事情。
在我的例子中,有一段(一列火车或一辆公共汽车)的旅程,包括一系列停靠点。这些停靠点中的每一个都会在发生时分配时间戳。现在,用户可以取消此旅程,从而导致所有时间戳从旅程中停止(简化)。
我要做的是发出一个" JourneyCancelled"事件
现在,如果有一个读取模型提供了旅程的停靠列表 - 读取模型将如何处理此事件?它可能还会重置旅程中每个停靠点的时间戳,这意味着它会复制那里的逻辑(以及处理此事件的每个其他读取模型)。
我感兴趣的是旅程被取消但我也对发生的细节感兴趣。
关于域名事件我做错了什么?
罗尼
答案 0 :(得分:6)
据我所知,这是一个很好的模式,从命令到域事件的1:1关系开始,以便域事件表达用户所做的事情。
重新认识 - 1:1是常见的,因为许多命令只修改单个实体并且不需要任何补偿操作,但它不是通用的。当用户提出更改时,您通常需要多个事件来维护不变量。
您还要小心“用户做了”。如果您的消息描述了用户(在现实世界中)已经完成的事情,则该消息是事件,而不是命令。命令是用户要求模型做某事(重要的试金石 - 允许说不的模型?)。
在我的例子中,有一段(一列火车或一辆公共汽车)的旅程,包括一系列停靠点。这些停靠点中的每一个都会在发生时分配时间戳。
请记住,“聚合”几乎总是信息资源 - 不是Traveler,而是TravelerProfile。不是旅程,而是预订或行程。
在这种情况下,“何时会发生”意味着计划或行程作为一个概念,应该在您的模型中明确(尚不清楚这个东西是您的聚合的根,还是其中的子实体)
您还需要考虑停止(我喜欢Legs,来自我在旅行软件中的工作)是值,还是当前状态的诱惑。
现在,用户可以取消此旅程,从而导致所有时间戳从旅途中止删除(简化)。
提示:在询问建模建议时,避免简化;良好的建模需要理解约束。
为什么取消旅程应该从时间表中删除时间戳,这一点都不清楚。
尝试猜测你想要的类比;在航空旅行中,旅行由腿组成。如果我们取消行程,我们还要取消支付腿票,释放座位分配等等。
所以选择座位分配作为具体的例子;取消行程意味着释放座位分配是一个不变的事实,因此应该有一个明确的事件,宣布座位分配已被取消(截至时间戳等)。
所以读取模型跟踪座位分配应该是监听座位分配事件,并适当地(明确地)更新座位图,而不是监听JourneyCancelled并推断座位的变化(隐式)。
换句话说,我们通过使事件流更加明确来逃避重复业务逻辑的陷阱。请注意我们免费获得的额外解耦:因为读取模型可以侦听特定于其关注的实体的事件,所以我们可以在写入模型中重新设计聚合,而不必担心对读取模型的影响。
答案 1 :(得分:3)
现在,用户可以取消此旅程,从而导致所有时间戳从旅程事件中删除(简化)。
事件应该是不可变的,您不应该根据后续事件从中删除数据。
现在,如果有一个读取模型提供了旅程的停靠列表 - 读取模型将如何处理此事件?它可能还会重置每次旅程的时间戳
这个阅读模型是什么?你想回答什么问题?业务是否需要一个读取模型,显示所有预测的在旅程中停止,或者JourneyCompleted
事件是否更合适? (或更精细,LegCompleted
事件)。
是否需要知道哪些旅程被取消,但是保持预测的停止?
一种方法是将事件分开一点,如上所述:
JourneyStarted
(可包括所有预测的停靠点)LegCompleted
(停止名称/ ID,时间戳等)LegCompleted
JourneyCompleted
或
JourneyStarted
LegCompleted
JourneyCancelled
等...
我不清楚你在读写方面做了什么。读取模型通常是 数据的聚合,旨在回答以下问题:
域的写入端用于强制执行业务规则和不变量。
答案 2 :(得分:3)
虽然你以命令>事件作为起点是正确的,但到目前为止它并不是一个教条。如果您查看任何事件源框架,他们总是假设域模型上的一个事务可能会返回事件列表。
说,一个命令可能导致生成许多事件。这些事件中的每一个都可以投射到读模型上的单个原子操作中。
在您的情况下,CancelJourney
命令将生成一个JourneyCancelled
加上大量StopTimestampCleared
个事件。因此,您的业务逻辑将采用聚合方法,并且您的预测将足够愚蠢,不会包含该逻辑。
记录导致聚合状态更改的任何事件非常重要。另外,尽量使Apply
方法尽可能小。在这种情况下,您会看到JourneyCancelled
与其他文件是分开的。此外,非常重要的是,请记住,Apply
方法可能会随着时间而变化,但您需要做好准备,每次阅读汇总时,都会再次播放。