【问题标题】:Should I put command bus between controller and domain service?我应该在控制器和域服务之间放置命令总线吗?
【发布时间】:2019-06-25 20:52:18
【问题描述】:

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

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

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

重要的是我需要命令的返回值,从实现的角度来看这不是问题,但在 CQRS 方面看起来不太好 - 我应该同时 ADD 和 GET。

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

这最后一个选项对我来说似乎相当不错,但我在请求生命周期内的明确实现方面存在问题。

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

【问题讨论】:

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


    【解决方案1】:

    虽然@cperson 的answer 在技术上是正确的,但我想添加一些细微差别。

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

    • Command 是基础架构层的一部分,Event 是域的一部分。
    • 因此,您应该从命令中触发AggregateRoot 上发出事件的代码。
    • 这可以通过 mergeObjectContext 或 eventBus.publish 完成(请参阅 NestJS docs)。
    • 可以从其他域对象应用事件,但聚合通常是发射器(提交时)。

    我想解决的另一点是假设事件源架构,即应用 CQRS/ES。虽然 CQRS 通常与事件溯源结合使用,但没有任何规定要这样做。事件溯源可以带来额外的优势,但也带来了显着增加的复杂性。您应该仔细权衡拥有 ES 的利弊。

    在许多情况下,您不需要事件溯源。只有 CQRS 已经给你带来了很多好处,比如让你的域/有界上下文得到很好的包含。读写分离、单一职责命令 + 查询(通常更 SOLID)、更清晰的架构等。在更高的层次上,更容易将焦点从 转移到“我如何实现这个(CRUD-wise)? ',到'这些用户需求如何适应领域模型?'。

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

    您始终可以从 CQRS 开始,然后在需要时添加事件溯源。

    【讨论】:

    • 感谢您的回答。我完全喜欢事件驱动的架构(从动作开始设计应用程序,通过事件进行通信)并且我用 cqrs 实现它,但我不喜欢 ES 本身。我同意这是一个很棒的概念(我在前端与 redux 一起使用它)但后端的复杂性对我来说太高了。
    【解决方案2】:

    随着架构的发展,如果您使用 Processes/Sagas 来管理工作流和聚合间通信,您可能会发现需要命令总线。如果是这种情况,那么将这条总线用于所有命令自然是有意义的。

    以下是我更喜欢的方法:

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

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

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

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

    【讨论】:

    • 感谢您的回答。所以你基本上证实了我的想法。这种方法的问题是我必须调整实现以及我如何进行客户端-服务器通信以适应这种模式。我希望使用 CQRS 创建我的后端/API,以便为快速业务逻辑更改开放。但是为了实现这种良好的解耦,我现在必须放弃标准的 REST 接口并实现 websockets——它们是很酷的技术,但对我来说可能有点矫枉过正。我希望我能以某种方式封装请求和响应周期之间的命令/事件模式。有什么想法吗?
    • @ŁukaszOstrowski 服用 CQRS 时需要考虑许多因素。我不知道快速的业务逻辑变化是使用这种模式的好处之一。您可以在标准的 n 层架构中应用 DDD,并放弃 CQRS 的开销。就 REST 而言,您通常会在提交命令时返回 201 Accepted。然后,您的客户端将等到命令完成后再继续。这可以是 websocket 或客户端轮询。或者您也可以处理来自控制器的命令并将结果返回给客户端。每种方法都有其权衡。
    • 好的,谢谢,我会考虑的。这个想法是我正在建立初创公司,我必须快速制作原型,但另一方面 - 事情可能会经常变化。通过实施 CQRS,我看到了解耦域的好处
    • 从事软件业务多年(使用 CQRS+ES),最近完成了我的第一个“创业”想法(使用标准 N 层,没有花哨),我可以肯定地说你应该决定是专注于创业还是专注于自己的技术发展。任何一个都是沿着你前进的道路前进的巨大动力,但实际上,这是两个相互竞争的优先事项。选择一个重要的并快乐。不要两个都选。
    • 我同意。我试图实现的是架构上的额外开销,所以我可以放弃更多的实现。事件驱动架构对我来说是新事物(我通常不做后端),但我已经看到了它的好处。没有花哨的东西,我不使用事件溯源,只是想要更多的声明性方法,我在 Redux Saga 的前端应用程序中使用它。我远没有过度工程,只是想建立一些稳定的基础。
    猜你喜欢
    • 2015-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多