【问题标题】:CQRS with MediatR and re-usability of commandsCQRS 与 MediatR 和命令的可重用性
【发布时间】:2018-11-27 05:28:54
【问题描述】:

创建只包含对象的命令有意义吗?例如:

public class CreateCommand : IRequest
{
   SomeDTO SomeDTO { get; set; }
}

public class UpdateCommand : IRequest
{
   SomeDTO SomeDTO { get; set; }
}

或者可能是这样的(推导):

public class UpdateCommand : SomeDTO, IRequest
{
}

或者命令/请求本身应该被视为 DTO?我很困惑,因为我看到了很多做事的方法。将所有属性复制到命令/请求类听起来也不是一件好事。

您如何在您的项目中做到这一点?

您是直接将您的命令映射到您的域模型还是使用命令只是为了传递 DTO?

如果使用 MVC 框架,我的控制器操作的输入应该是什么?它应该是一个命令,还是应该在我的操作实现中创建命令并发送它? (我想这将取决于我如何建模我的命令)

【问题讨论】:

  • 该命令应该代表读/写操作的合约。由于可能需要多个属性来表示命令和关注的隔离,我更喜欢第一个选项(命令持有对象)。命令和请求应被视为将数据从客户端发送到服务器的合同,反之亦然。 DTO(就数据库而言)应该不同于命令并映射到模型/实体。 (所有这些类比都是基于我的偏好)
  • 虽然应用于基本聚合的一些(少数)命令可能与 DTO 有很多相似之处(例如,CreateHouseCommand 可能具有具有大部分 House 属性的有效负载),但大多数命令的重量应该比完整的House 域模型轻得多,例如PaintHouse(colour: blue) 和像 OpenDoor 这样的命令可能根本没有额外的有效载荷。将完整的 House 模型用作像 PaintHouseOpenDoor 这样的命令的命令负载是没有意义的。
  • @StuartLC 感谢您的示例 :)

标签: c# domain-driven-design cqrs dto mediatr


【解决方案1】:

创建只包含对象的命令有意义吗?

不,额外的类没有任何价值:没有语义,没有行为......

或者命令/请求本身应该被视为 DTO?

命令(在术语的CQRS sense 中)本质上是 DTO。它们是在层/层之间循环的哑数据包。

您是否将您的命令直接映射到您的域模型

这取决于您是否喜欢task-based UI 而不是基于 CRUD 的 UI。如果你做 DDD/富域模型——有些人甚至会说基本的 OO 封装——你不会映射它们。命令名称可能会匹配实体方法,但它们的内容不会自动映射到域模型字段。

如果使用 MVC 框架,我的输入应该是什么 控制器动作?它应该是命令,还是我应该创建命令 在我的动作实现中并发送它?

我会说两者都是合法且适用的,除了 MVC 模型绑定的偶尔技术怪癖。

【讨论】:

  • 我不认为命令是 DTO。我认为命令只是用于写入数据的方法或操作。
  • “CQRS 是一种将读取数据(查询)的操作更新数据(命令)的操作分开的模式”docs.microsoft.com/en-us/azure/architecture/patterns/cqrs
  • 您混淆了形式和功能。在形式上,它是一个 DTO。另请参阅此处的第一个答案:stackoverflow.com/questions/30095966/…
  • 当然。根据 Go4 的定义,它不是 DTO。在 CQRS 中,确实如此。
  • 不,绝对不是。你让自己被metonymies 愚弄了。当微软说命令是“更新数据的操作”时,它们的真正意思是“用于更新数据”。命令处理程序更新数据,而不是命令。看代码。
【解决方案2】:

至少在我的世界里,命令和域对象有不同的设计约束。特别是,命令是 API 表面的一部分——它们是与其他服务的合同的一部分——因此需要在很长一段时间内具有兼容的定义。另一方面,域对象是我们当前做事方式的本地对象 - 它们是我们在黑盒子组织数据的一部分。所以我们可以按照我们喜欢的任何节奏来改变它们。

跨越进程边界的命令是消息,也就是说byte[]s。这一点需要在形式和语义上保持稳定。

byte[] 与域无关,在“解析”消息时通过其他几个与域无关的中间阶段是很常见的

byte[] -> utf8
utf8 -> DOM
DOM -> Dictionary
...

但我们通常会推动合同的域特定表达。

参见,例如Mark Seemann

在边界上,应用程序不是面向对象的。 DTO 是映射到面向对象语言中的此类数据的表示。

byte[] 强制转换为便于查询的形式后,然后我们可以开始考虑是否要使用该数据来开始初始化“对象”。

您可能会问的另一个问题 - 将消息数据包含在通用元数据“信封”中是否有价值。这种模式一直都在发生——最熟悉的例子是 HTTP POST 是一组附加到消息正文的通用标头。

数据和元数据当然是不同的关注点;在您的解决方案中保持它们的区别绝对是有意义的。

我认为组合数据结构,而不是继承它们,将是更易于维护的选择。

public class Envelope<Message> ....

可能是一个合理的起点。

【讨论】:

  • 那么我展示的第一个例子是要走的路吗?
【解决方案3】:

您应该将该命令视为指示您的域执行某项操作的“口头语句”。例如,“UpdateCommand”指示您的域更新某些内容。在命令中,您应该包含命令的细节(在您的情况下 dto 很好)...

但是要非常小心那些 DTO。您不希望您的域依赖于 MVC,反之亦然。确保 dto 所在的程序集的级别(在 MVC 的方向上)不高于域逻辑。

在您的 MVC 中,您应该只有:

  • 依赖注入设置
  • 控制器和视图

控制器应该只包含从方法(http)参数(女巫不安全)转换为域所需的 dto 并调用域所需的代码。

至少我是这样做的。

【讨论】:

    猜你喜欢
    • 2021-06-22
    • 2020-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-09
    • 2020-11-09
    • 2017-04-21
    • 1970-01-01
    相关资源
    最近更新 更多