CQRS 架构中的条件“创建”命令

Conditional `create` commands within a CQRS architecture

我会简化我的问题:

我的 LightsState API 可以接收 2 种类型的输入:lightOn {lightId: ##}lightOff {lightId: ##}。 (AMQP 输入但与此处无关)

这些输入可以很好地转化为 2 个命令:TurnLightOnCmdTurnLightOffCmd

这些命令将创建 2 个事件:LightTurnedOnEventLightTurnedOffEvent

这些事件将应用于 Light Aggregate,并且持久投影将是 state of the light

到这里为止都很好。

但是因为没有输入:create light,我无法从中生成 CreateLightCmd。当我收到带有新 lightIdlightOn 输入时,我只能调用 CreateLightCmd 来创建 Light Aggregate,然后在其上应用 TurnLightOnCmd

我不确定如何处理这个问题以及如何遵循良好的 CQRS 实践。 是否可以从命令端调用查询端以检查是否 light exists by id 然后在需要时先调用 CreateLightCmd ? 或者我应该从命令端进行数据库查询并保持命令端和查询端分离? 或者还有其他解决方案吗?

谢谢

看看 Udi Dahan 的开创性作品 post,"Don't create aggregate roots" [0]。重点是:

  • 不要创建聚合根
  • 始终获取一个实体

这意味着当您发出命令时,您应该始终拥有一个聚合,其中的实际聚合根实体已初始化为默认状态。在您的例子中,默认状态是 "light off"。您在此灯上执行命令,现在它处于 "light on" 状态。现在将它保存到数据库并创建它。除非您的领域关心 LightCreatedEvent,否则您不需要对其建模。

[0] http://udidahan.com/2009/06/29/dont-create-aggregate-roots/

Is it ok to call the Query side from Command side to check if light exists by id and then invoke CreateLightCmd first if needed?

不是真的 - 引入了竞争条件,其后果可能不会让你开心。

评论:DDD+CQRS+ES 在架构上与单独的 DDD 非常相似。基本概念是我们将信息保存到我们的存储设备中(又名 "the database")。该模型在从数据库加载当前状态的进程中运行,使用命令计算新状态,然后将该新状态存储在数据库中。

当我们进行事件溯源时,同样的模式也适用 - 我们从用于写入的数据库中读取历史记录,计算新事件,并将这些事件附加到历史记录中。

但是:创建模式很奇怪

当我们尝试查询以前从未见过的标识符的历史记录时,我们将得到一个空值,或者 None,或者其中没有任何事件的历史记录,或类似的东西。

令人惊讶的是:很好

对于您的用例,您不一定需要 CreateLightCmd - 相反,当您获得应隐式创建的其他命令之一时,您希望生成一个新的 LightCreatedEvent轻.

在伪代码中:

TurnLightOn (cmd) {
    history = getHistory(cmd.lightId)
    if (history.isEmpty) {
        history.append(LightCreatedEvent.from(cmd))
    }
    history.append(LightTurnedOnEvent.from(cmd))
    save(cmd.lightId, history)
}