CQRS 和命令、CommandHandler 和聚合验证
CQRS and Command, CommandHandler and Aggregate validation
我正在迈出在新项目中实施 CQRS 的第一步。
按照 CQRS 方法,我使用 MediatR 作为框架来协调我的命令。
我还使用 FluentValidation 和 ValidationBehavior 来实现命令验证。但是我正在努力定义将哪个验证放在哪里:
- 在命令中(使用带有 FluentValidation 的 CommandValidator)
- 在命令处理程序中,作为自定义检查
- 在聚合根中(直接在此聚合上调用操作时)
我想了解将哪个验证放在哪里:
- 字段验证(长度、格式、必填...)
- 需要实现 IRepository 的业务规则验证(例如,基于列的唯一性,而不是基于主键)
- 不需要实施 IRepository 的业务规则验证
因为我目前已经实现了它,所以我无法访问我的 aggregate 根中的 IRepository 实现。命令处理程序通过 DI 注入此存储库,它们在聚合根上执行操作,但也负责调用例如保存或添加以最终更新数据库。
在 "ideal" CQRS 中,您的聚合状态应包含执行命令所需的所有内容,并且您不应在命令执行期间与其他聚合通信(因为它们超出了一致性边界)
因此业务规则验证(决定您是否可以执行给定命令)应该转到聚合的命令处理程序。
在发送命令之前应检查命令格式规则(必填字段、允许值)- 在客户端上,以及在将命令传递给命令处理程序之前(因为我们不能完全信任客户端)。
因此,我会将命令格式验证规则保留在更接近命令定义的地方——可能采用声明形式,而不是作为专用验证器。
在某些情况下,如果不查询其他聚合的状态,聚合就无法完全检查命令的正确性。这里是good article about inter-aggregate communication。简而言之 - 你不应该在命令执行期间与其他聚合对话。
经典示例是创建具有唯一名称的用户。社区共识似乎是:
您验证用户名在客户端中是唯一的 - 在发送命令之前。
用户聚合执行命令而不检查唯一性。
极少数情况下,有人在您发送命令之前注册了相同的用户名,这些情况在投影代码中被捕获,并导致手动解决的异常。或者有一些 saga 会监视重复的用户名并以某种方式处理这个问题。
因此 - 需要聚合间通信的业务规则验证最好放入 Saga/Process Manager
我正在迈出在新项目中实施 CQRS 的第一步。 按照 CQRS 方法,我使用 MediatR 作为框架来协调我的命令。
我还使用 FluentValidation 和 ValidationBehavior 来实现命令验证。但是我正在努力定义将哪个验证放在哪里:
- 在命令中(使用带有 FluentValidation 的 CommandValidator)
- 在命令处理程序中,作为自定义检查
- 在聚合根中(直接在此聚合上调用操作时)
我想了解将哪个验证放在哪里:
- 字段验证(长度、格式、必填...)
- 需要实现 IRepository 的业务规则验证(例如,基于列的唯一性,而不是基于主键)
- 不需要实施 IRepository 的业务规则验证
因为我目前已经实现了它,所以我无法访问我的 aggregate 根中的 IRepository 实现。命令处理程序通过 DI 注入此存储库,它们在聚合根上执行操作,但也负责调用例如保存或添加以最终更新数据库。
在 "ideal" CQRS 中,您的聚合状态应包含执行命令所需的所有内容,并且您不应在命令执行期间与其他聚合通信(因为它们超出了一致性边界)
因此业务规则验证(决定您是否可以执行给定命令)应该转到聚合的命令处理程序。
在发送命令之前应检查命令格式规则(必填字段、允许值)- 在客户端上,以及在将命令传递给命令处理程序之前(因为我们不能完全信任客户端)。
因此,我会将命令格式验证规则保留在更接近命令定义的地方——可能采用声明形式,而不是作为专用验证器。
在某些情况下,如果不查询其他聚合的状态,聚合就无法完全检查命令的正确性。这里是good article about inter-aggregate communication。简而言之 - 你不应该在命令执行期间与其他聚合对话。
经典示例是创建具有唯一名称的用户。社区共识似乎是:
您验证用户名在客户端中是唯一的 - 在发送命令之前。
用户聚合执行命令而不检查唯一性。
极少数情况下,有人在您发送命令之前注册了相同的用户名,这些情况在投影代码中被捕获,并导致手动解决的异常。或者有一些 saga 会监视重复的用户名并以某种方式处理这个问题。
因此 - 需要聚合间通信的业务规则验证最好放入 Saga/Process Manager