我最近阅读了CQRS à la Greg Young的文章,我仍在努力了解 CQRS。
我不确定应该在哪里进行输入验证,以及是否可能必须在两个不同的位置进行(因此违反了不要重复自己的规则,也可能违反了关注点分离)。
给定以下应用程序架构:
# +--------------------+ ||
# | event store | ||
# +--------------------+ ||
# ^ | ||
# | events | ||
# | v
# +--------------------+ events +--------------------+
# | domain/ | ---------------------> | (denormalized) |
# | business objects | | query repository |
# +--------------------+ || +--------------------+
# ^ ^ ^ ^ ^ || |
# | | | | | || |
# +--------------------+ || |
# | command bus | || |
# +--------------------+ || |
# ^ |
# | +------------------+ |
# +------------ | user interface | <-----------+
# commands +------------------+ UI form data
该域隐藏在命令总线后面的 UI 中。也就是说,UI 只能向域发送命令,而不能直接访问域对象。
当聚合根对事件做出反应时,不得进行验证,但更早。
命令被转换为域中的事件(通过聚合根)。这是可能发生验证的地方:如果无法执行命令,则不会将其转换为相应的事件;相反,(例如)抛出一个异常,该异常通过命令总线向上冒泡,返回 UI,在那里它被捕获。
问题:
如果命令无法执行,我想禁用 UI 中的相应按钮或菜单项。但是我怎么知道一个命令在发送之前是否可以执行呢?查询端在这里对我没有帮助,因为它不包含任何业务逻辑;我在命令方面所能做的就是发送命令。
可能的解决方案:
对于任何命令DoX,引入一个相应的虚拟命令CanDoX,它实际上不会做任何事情,但让域反馈命令X是否可以无错误地执行。
在 UI 中复制一些验证逻辑(真正属于域)。
显然第二种解决方案是不利的(由于缺乏关注点分离)。但是第一个真的更好吗?