CQRS组件在REST API中的角色和职责

Ale*_*one 8 design-patterns domain-driven-design cqrs

关于带有DDD的CQRS以及构成每个组件的内容,有很多意见.我还没有开始研究事件采购,所以下面的列表中没有包含与此相关的任何内容.虽然对ES的深入了解会很有趣.

到目前为止,我有以下相关责任的组件(见下文).我在下面的几点中概述了一些问题.

REST端点/应用程序

  • 接收来自用户/ ui/etc的请求
  • 构建和发送相关命令
  • 如果命令需要来自其他有界上下文的值,则执行正确实例化命令所需的相关Finder调用(例如,Order需要用户ID)
  • 在GET的情况下:调用相关的Finder
  • Finders坐在应用程序的这个级别.有界上下文写入方(命令处理程序,聚合,工厂,域服务等)不应该调用Finders.这将保持封装并通过仅将所需数据(而不是完整的DTO)传递给命令,它将成为适度的反腐败层.
  • 例如:

AggregateId orderId = AggregateId.get(); AggregateId userId = finder.findUserAggregateIdByEmail(email); dispatcher.fire(new CreateOrderCommand(orderId,userId,orderItems));

命令

  • 通过调度命令对域进行更改
  • 命令是不可变的,包含有界上下文所需的数据,以改变它的状态或抛出异常
  • 可以在创建对象时验证命令输入,以避免发送无效命令
  • 例如: new CreateOrderCommand( orderId, userId, orderItems );

命令处理程序

  • 处理程序可以成功应用命令或引发异常
  • 每个命令只能有一个命令处理程序
  • 处理程序将加载或创建聚合根(存储库或聚合工厂)
  • 处理程序将该命令应用于聚合根
  • 处理程序处理存储库
  • 不应该触发命令(在其有界的上下文中)
  • 命令处理程序应该调度事件吗?例如,成功保存到数据库后?或者这仅仅是Aggregate的责任?

聚合工厂

  • 封装正确初始化聚合根所需的逻辑
  • 工厂可以访问存储库
  • 工厂应该访问域名服务吗?
  • 例如: factory.createOrder( orderId, userId, orderItems );

聚合根/聚合

  • 包含域逻辑,状态和行为
  • 负责调度事件
  • Aggregate Root封装了对Aggregates的访问
  • Aggregate Root应具有唯一标识它的ID
  • 不应与外部服务(事件发布者除外)交互
  • 例如: order.cancel();

域名服务

  • 这包含不太适合聚合根的内容
  • 域服务可以与哪些组件进行交互?
  • 域服务是否应该触发命令/事件?
  • 例如:不确定在这里使用什么,上面的第一点充其量是模糊的.大多数行为都很好地存在于Aggregate中,或者可以通过Sagas/Events/Commands实现.这里有什么有效的例子?

知识库

  • 负责加载/保存/更新/等我们的聚合
  • 例如: repo.load(orderId);

事件

  • 表示在聚合(或命令处理程序等,如果它们也可以触发事件)中发生的事情
  • 事件是不可改变的
  • 系统中的其他有界上下文可以使用事件来做出决策
  • 例如: new OrderCancelledEvent( orderId );

事件处理程序

  • 对发生的事件做出反应
  • 在相同或不同的有界上下文中,单个事件可以有多个事件处理程序
  • 可以与基础架构服务交互:OrderCancelled => OrderCancelledHandler => EmailService.sendEmail()
  • 可以触发新命令
  • 可以和Finders交谈
  • 当事件处理程序触发命令,与Finders对话并与基础结构交互时,它在本质上与Saga(或REST Enpoint行为)类似.除了它是对单个事件的反应,而不是对一系列事件的反应.

冒险故事

  • 维护一个跨越相同或多个有界上下文(坐标)的业务流程
  • 收到活动
  • 维护链/事件集的状态
  • 通常状态是持久的
  • 可以设置超时以检查/更改状态(可以有时间概念)
  • 国家可以有副作用,例如:发射命令,与Finder交谈,与基础设施服务交互(例如电子邮件)
  • 例如:等待OrderShipped和OrderReceived事件=> fire CancelOrderCommand等待OrderCancelled =>取消订单取消电子邮件

发现者

  • 用于检索上下文的readmodel
  • 通常返回数据传输对象(DTO)类型对象
  • 在我们的应用程序的写入端不应找到Finder(耦合较少)
  • 单(读+写)规范化数据库模型:Finder可以调用其他Finder(跨上下文)来满足嵌套对象
  • 读取特定的非规范化数据库模型:Finder将在一个数据库调用中获取所有数据
  • 例如: finder.findOrdersOnDate( date );

基建服务

  • 处理基础设施:数据库访问,发送电子邮件,消息队列等

这是组件与责任的准确摘要吗?缺少什么,应该移动什么?我可以用相关答案更新列表.

MeT*_*tus 3

就像你说的,有很多意见,你需要过滤它们,因为大多数时候人们在没有任何经验的情况下发表意见。CQRS 是一个大主题,因此我认为如果没有相关经验,您不应该完全跳入 DDD 和 ES。服务应该被很好地包含并具有明确定义的边界,如果您遵循这些原则,您将能够在您的域中拥有不同的实现,因此现在只从 CQRS 开始,一旦掌握了 CQRS,就将 DDD/ES 添加到以下服务中。

我建议您开始构建架构的 CQRS 部分,一个用于命令的网关和一个用于查询的网关,因为这很常见,而且需要做出很多决定:

  • 休息API

  • 消息合约/验证

  • 读取模型...

并开始以更传统的方式实现您的服务,无需 DDD,只需使用存储库模式。当你开始感到自信时,也许你可以在聚合方面跳入 DDD,然后再跳入 ES。您随时可以在稍后阶段更改初始服务。

我的建议是不要尝试一次完成所有事情,因为你会失败;我以前已经见过很多次这样的事情了。

例如:等待 OrderShipped 和 OrderReceived 事件 => 触发 CancelOrderCommand 等待 OrderCancelled => 触发订单取消电子邮件

Sagas 不应该发布事件(saga 模式),saga 聚合事件并提交命令。事实上,像 NServiceBus 这样的框架允许 sagas 发布事件并没有帮助,所以要注意。

单一(读+写)规范化数据库模型:Finder 可以调用其他 Finder(跨上下文)来满足嵌套对象

您希望在阅读模型中拥有哪些其他上下文?

基础设施服务

Deals with infrastructure: db access, send emails, message queues, etc
Run Code Online (Sandbox Code Playgroud)

不知道你的意思是什么,但它看起来确实不对。消息队列还是数据库服务??