我应该在控制器和域服务之间放置命令总线吗?

时间:2019-06-25 20:52:18

标签: node.js design-patterns crud cqrs nestjs

我正在后端上,尝试实现CQRS模式。 我对事件很清楚,但有时会遇到命令困扰。

我已经看到命令是由用户请求的,例如ChangePasswordCommand。但是,在实现级别,用户只是调用由某个控制器处理的端点。

我可以向控制器注入UserService,该控制器将处理域逻辑,这就是基本教程的工作方式(我使用Nest.js)。但是我觉得也许这是我应该使用命令的地方-所以我应该在控制器中执行命令ChangePasswordCommand,然后域模块会处理它吗?

重要的是我需要从命令返回值,从实现的角度来看这不是问题,但是就CQRS而言,它看起来并不好-我应该同时添加和获取。

或者最后一个选择是在控制器中执行命令,然后在命令处理程序中发出事件(PasswordChangedEvent)。接下来,等到事件回来并返回控制器中的值。

这最后一个选项对我来说似乎很好,但是我在请求生命周期内实现清晰的实现时遇到了问题。

我基于 https://docs.nestjs.com/recipes/cqrs

2 个答案:

答案 0 :(得分:2)

尽管@cperson的answer在技术上是正确的,但我想在此添加一些细微之处。

首先,从答案描述中可能不清楚的地方是建议“在命令处理程序中发出事件(PasswordChangedEvent)” 。这也是我更喜欢的,但请注意:

  • Command是基础结构层的一部分,而Event是域的一部分。
  • 因此,您应该从命令中触发发出事件的AggregateRoot上的代码。
  • 可以使用mergeObjectContexteventBus.publish(请参见NestJS docs)来完成此操作。
  • 可以从其他域对象应用事件,但是事件通常是发射器(提交时)。

我要解决的另一点是假设采用事件源架构,即应用CQRS / ES。尽管CQRS通常与事件来源结合使用,但没有任何规定。事件源可以带来其他优势,但同时也会带来极大的复杂性。您应该仔细权衡使用ES的利弊。

在许多情况下,您不需要事件源。仅拥有CQRS已经为您带来了很多好处,例如拥有完善的域/边界上下文。读写之间的分隔,单一职责命令+查询(通常使用更多SOLID),更简洁的体系结构等。在更高级别上,更容易将重点从转移到我该如何实现这一目标(在CRUD方面)? ',以'这些用户要求如何适应域模型?'

没有ES,您可以拥有一个关系数据库,例如坚持使用TypeORM。您可以保留事件,但这不是必需的。在许多情况下,您可以避免客户端需要订阅事件的最终一致性(也许您只是使用它们来驱动传奇,并更新读取的视图/投影)。

您总是可以仅从CQRS开始,并在需要时稍后再添加事件源。

答案 1 :(得分:1)

随着体系结构的发展,如果您正在使用Processes / Sagas来管理工作流和聚合之间的通信,则可能会发现您需要命令总线。如果是这样,那么在所有命令中使用该总线自然是有意义的。

以下是我希望使用的方法:

  

在控制器中执行命令,然后在命令处理程序中发出一个事件(PasswordChangedEvent)。接下来,等到事件回来并返回控制器中的值。

关于实现细节,在.NET中,我们使用SignalR Websockets服务,该服务将读取事件总线(发布所有事件的位置),并将事件转发给已订阅事件的客户端。

在这种情况下,工作流程为:

  1. 用户发布到控制器。
  2. 控制器将命令附加到命令总线。
  3. 控制器返回一个标识命令的ID。
  4. 客户端(浏览器客户端)订阅与此命令有关的事件。
  5. 该命令由域服务接收并处理。事件被发送到事件存储。
  6. 该事件已发布到事件总线。
  7. 事件侦听器订阅服务接收事件,找到订阅,然后将事件发送给客户端。
  8. 客户端接收事件并通知用户。