【问题标题】:Should this Process Manager state be persisted?是否应该保持此流程管理器状态?
【发布时间】:2020-05-16 06:06:51
【问题描述】:

我正在开发一个事件源电动汽车充电站管理系统,该系统连接到多个充电站。在这个领域中,我提出了充电站的聚合,其中包括充电站的内部状态(是否连接,其连接器的内部状态)。

我可以向 Station 聚合发出的命令是:

  • UnlockConnector,它发出StationConnectorUnlocked

  • StopConnectorEnergyFlow,它发出StationConnectorEnergyFlowStopped

我想出了另一个代表充电会话的聚合,即用户和充电站连接器之间的交互。充电会话的创建与这些事件相结合,即如果连接器已被用户解锁,则创建会话,如果连接器的能量流已停止,则充电会话已完成。

我添加了一个监听事件的进程管理器:

  • StationConnectorUnlocked(stationID, connectorID) -> SessionCreated(new uuid())

  • StationConnectorEnergyFlowStopped(stationID, connectorID) -> SessionFinished(id???)

对于第一个事件,创建会话非常简单。但是对于后者,它必须知道正在进行的会话的sessionID 发生在Station(stationID)Connector(connectorID) 上,因此它可以更新会话。

我不能简单地实现GetSessionByConnectorID 函数,因为我正在实现一个事件源系统,我可以获得会话的事件流的唯一方法是通过它的 ID,而不是通过它的 ConnectorID(因为我只知道会话的连接器ID,当我将它水化时),所以我不知道如何实现GetSessionByConnectorID 函数。

因此,进程管理器有一些内部内存状态((stationID, connectorID) -> (sessionID)),以跟踪sessionID。但是,由于它在内存中,一旦进程管理器崩溃,我就失去了(stationID, connectorID) <-> (sessionID) 之间的关联,并且我无法再正确响应 ConnectorEnergyFlowStopped 事件。

我应该如何处理?我应该坚持流程管理器状态吗?我是否应该通过流程管理器事件坚持它(这会很尴尬,因为它与通用语言没有很好的相关性,我在想SessionProcessManagerReceivedStationConnectorUnlockedEvent-awkward-level)

编辑 - 新想法

我想到了别的办法,那就是删除内部进程管理器状态,并将 (stationID, connectorID) <-> (sessionID) 的关联放入 Station 聚合内。不幸的是,这意味着更高的耦合(Station 必须知道何时创建会话,以及如何生成其 ID),但我认为它可能更简单(因为 Station 的事件是持久的)。因此,Station 会发出与会话相关的事件,其中包含 sessionID:

  • StationConnectorUnlocked(stationID, connectorID, sessionID) -> SessionCreated(sessionID)

  • StationConnectorEnergyFlowStopped(stationID, connectorID, sessionID) -> SessionFinished(sessionID)

但是,将这两个东西混合起来似乎有点奇怪,即使它只是会话的 ID。

【问题讨论】:

  • 图表会有所帮助。事件流程很难理解。我 95% 确定您不需要流程管理器。顺便说一句,流程管理器总是保持状态。但我认为这与这里无关。所以,问题标题的答案是“是”,但你的问题是建模问题,
  • 正如我在另一个问题中提到的,我不知道在 ES 中解决这个问题的正确方法,如果没有 ES,我只会通过 ConnectorId 查询会话。所以,我刚刚有了一个想法:鉴于从 ConnectorId 到 SessionId 的映射是一个相对较短的需求,为什么不简单地将它存储在分布式缓存(Redis)中呢?您可以完全摆脱此流程管理器,只需实现使用 sessionRepository.GetByConnectorId() 的事件处理程序,并且在内部此 repo 可以使用缓存将 ConnectorId 转换为 SessionId。这使解决方案更简洁,并隐藏了 ES 技术需求
  • @AlexeyZimarev 我已经添加了一个图表来更清楚地解释它
  • @FrancescCastells 是的,这是有道理的,这实际上只是我需要的一个简单映射。我添加了一个新思路,但我认为这会导致更高的耦合度。

标签: events domain-driven-design event-sourcing saga process-management


【解决方案1】:

对我来说,流程管理器只是妨碍了这里。聚合不应该从任何地方产生,我认为你过度担心Station 知道Session

为什么不像session = station.unlockConnector(sessionId) 那样富有表现力?在 AR 上使用工厂方法来创建新的相关 AR 是很常见的,除非 Session 是完全独立的 BC。

此外,Session 的生命周期似乎与Station 密切相关。当连接器关闭时,您能负担得起最终的一致性吗?如果会话在连接器关闭的同时发生突变怎么办?

【讨论】:

  • 这看起来是个不错的方法。如果 unlockConnector 将创建一个新会话,传递 sessionId 需要什么?甚至,unlockConnector 可以决定返回前一个会话,如果它没有被终止,例如不需要新的 sessionId。
  • @FrancescCastells ID 生成通常可以被视为不应成为 AR 的一部分的基础设施细节,除非它们是用于 AR 中本地化的实体。无论 ID 是客户端生成的还是服务器生成的,我总是将 ID 传递给 AR。此外,如果您使用 GUID,那么客户端可以生成具有一些优势的 ID。它使服务器免于在 ack 响应中返回 ID。它使您可以更轻松地迁移到异步 CQRS。它还可以在处理命令时实现幂等性。
  • 如果客户端(站)发送 OpenSessionCommand 对我来说更有意义,但在这里它发送 UnlockConnectorCommand 并且客户端似乎不需要了解会话。如果 Station AR 负责创建 Session,对我来说外部元素传递 Id 似乎很奇怪,因为这意味着它需要知道 Station 是否会创建新的 Session。另一种方法是使用空 Id 创建 Session,并由持久化它的基础设施生成它。
  • 当您不了解该域时,很难对其进行推理。会话有哪些行为?等待 ID 的持久性并不能很好地处理事件。当您还没有 ARs ID 时,如何在模型中发布事件?
  • 我已经寻求解决方案,即 Station 聚合知道给定连接器的 SessionID,并在其 ConnectorUnlocked 和 ConnectorEnergyFlowStopped 事件中发送该 SessionID
【解决方案2】:

我可以看到很多解决此问题的方法,但我不熟悉您的领域,所以在这里提出想法:

  • 使用进程管理器。流程管理器处理长时间运行的流程并且需要保持状态 _by 定义。消息驱动的流程管理器通常甚至不需要在消息之间运行,因此它们处理事件、发出命令并关闭直到下一条消息。或者最终确定。据我了解,这是最初的问题。

  • 为每个会话从工作站发出会话 UUID。然后,工作站将知道会话 ID,并且不需要进程管理器。

  • 使用查询模型。使用您需要的所有信息投影会话开始事件。当能量流停止时查询它以找出给定站点和连接器的正在进行的会话,您甚至可能不需要用户 ID。投射sessionClosed事件并删除读取模型。

如果可能,我会选择第二个。最后一个将在我的列表中排在第二位,由于相关的复杂性,流程管理器将是最后的手段。

【讨论】:

  • 我认为第二种选择是最简单、最直接的。我唯一担心的是它使 Station 和 Session Aggregate 更加耦合,因为 Station 必须知道何时创建了会话。我可能会想得太多,就像我一样,但这似乎也是最小的耦合。你怎么看?
【解决方案3】:

我认为这个问题应该从自动售货机的角度来考虑,希望下面的答案可以回答你设计的聚合。 https://sourcemaking.com/design_patterns/state

【讨论】:

    猜你喜欢
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 2016-08-27
    相关资源
    最近更新 更多