like-persistence (cqrs?) 事件采购和副作用

akka-persistence (cqrs?) eventsourcing and side effects

我正在尝试弄清楚如何使用持久性参与者对远程 "IoT" 设备的状态进行建模,例如:

用户想开一盏灯,所以我们最合乎逻辑。

  1. 用户发送OnCommand
  2. persistent actor 接收命令,生成 LightTurnedOnEvent 并将其状态更新为 on

这是有道理的,但这里的问题是灯实际上从未打开过。好的,那么我们构建一个知道低级硬件控制巫术的 LightControlActor。这个 actor 监听 LightTurnedOnEvent,当它得到它时它会做事并打开灯。

真棒,现在我们开了一盏灯!但是不开心。 LightTurnedOnEvent有点躺在这里,还没开灯呢。按照这个逻辑 LightTurnedOnEvent 应该由 LightControlActor 生成,我的持久演员应该生成一些 SentRequestToTurnOnLight 但现在这对我来说变得复杂了所有不同的语义。

  1. 用户发送OnCommand
  2. 持久性 actor 接收 OnCommand 生成 RequestedLightTurnOnEvent 并将状态设置为挂起。

  3. LightController 在 RequestedLightTurnOnEvent 上启动并尝试打开外部系统上的灯。

然后呢?现在我该如何更新持久化 actor 的状态?让 LightController 发送一些奇怪的命令 SetStateToOnCommand?

那么当灯实际打开时如何更新持久状态?

一个想法是为您的活动使用 "saga" 之类的东西。

LightController: State idle
lightController ! OnCommand
    persist(LightTurnOnAttempted)
    lightControl ! LightTurnOnCommand
    become(pending)

LightControl:
lightControl ! LightTurnOnCommand
    performLightTurnOnAsyncFunction.map(_ => TurnOnLightCommand) pipeTo lightController

LightController: State pending
lightController ! TurnOnLightCommand
    persist(LightTurnedOn)
    become(initialized)

这为您提供了细粒度的控制。万一死机,在恢复过程中,可以查看灯是否亮起,或者LightController是否处于pending状态。如果它处于挂起状态,您可以重新发送 LightTurnOnCommand.

但您永远无法确定灯是否亮起,即使硬件控制器这样说,即灯泡可能已损坏。

关于事件的想法是它们在 有界上下文 中的含义。在您的情况下, LightTurnedOnEvent 由持久性参与者拥有,并且仅在其上下文中具有严格的含义。从所有者 actor 的 POV 来看,灯 应该 亮起并且未来 TurnOnCommand 不会改变此状态,不会发出新事件(它是幂等的)。

如果您希望此事件有其他含义,您需要一个 Saga(或任何您想称呼它的东西),它会对此事件做出反应,并将另一个命令发送给另一个 actor,在另一个上下文中,即在硬件上下文。这个 HW actor 会发出自己的事件,仅在其上下文中相关,即 LightBulbCoupledToPowerSource 或类似的东西;它甚至可以像另一个一样被命名为 LightTurnedOnEvent,但是在一个单独的 命名空间 中,很可能与其他属性一起命名。

为了更好地了解 concerns/contexts 的这种分离,我们可以想象这样一种情况,即 Saga 响应 LightTurnedOnEvent 会向 3 个不同的灯泡发送 3 个命令,在一个大房间里例如。