CQRS 架构中的条件“创建”命令
Conditional `create` commands within a CQRS architecture
我会简化我的问题:
我的 LightsState API 可以接收 2 种类型的输入:lightOn {lightId: ##}
和 lightOff {lightId: ##}
。 (AMQP 输入但与此处无关)
这些输入可以很好地转化为 2 个命令:TurnLightOnCmd
和 TurnLightOffCmd
。
这些命令将创建 2 个事件:LightTurnedOnEvent
和 LightTurnedOffEvent
。
这些事件将应用于 Light Aggregate
,并且持久投影将是 state of the light
。
到这里为止都很好。
但是因为没有输入:create light
,我无法从中生成 CreateLightCmd
。当我收到带有新 lightId
的 lightOn
输入时,我只能调用 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)
}
我会简化我的问题:
我的 LightsState API 可以接收 2 种类型的输入:lightOn {lightId: ##}
和 lightOff {lightId: ##}
。 (AMQP 输入但与此处无关)
这些输入可以很好地转化为 2 个命令:TurnLightOnCmd
和 TurnLightOffCmd
。
这些命令将创建 2 个事件:LightTurnedOnEvent
和 LightTurnedOffEvent
。
这些事件将应用于 Light Aggregate
,并且持久投影将是 state of the light
。
到这里为止都很好。
但是因为没有输入:create light
,我无法从中生成 CreateLightCmd
。当我收到带有新 lightId
的 lightOn
输入时,我只能调用 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)
}