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

时间:2014-04-23 04:20:24

标签: design-patterns domain-driven-design cqrs

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

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

REST端点/应用

  • 收到用户/ ui / etc
  • 的请求
  • 构建和发送相关命令
  • 如果命令需要来自其他有界上下文的值,则执行正确实例化命令所需的相关Finder调用(例如,订单需要用户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的访问权限
  • 聚合根应具有唯一标识它的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 );

基础设施服务

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

问题

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

1 个答案:

答案 0 :(得分:3)

就像你说的那样,有很多意见,你需要过滤它们,因为大多数时候人们在没有任何经验的情况下发表意见。 CQRS是一个很大的话题,所以我不认为没有经验,你应该完全跳入DDD和ES。服务应该包含得很好并且边界清晰,如果你遵循这些原则,你就可以在你的域中实现不同的实现,所以现在就开始使用CQRS,一旦你掌握了,就把DDD / ES添加到以下服务中CQRS。

我建议你这样开始构建你的架构的CQRS部分,一个命令的网关和一个用于查询的网关,因为这很常见,只是因为有很多决定要做:

  • Rest API

  • 消息合同/验证

  • 阅读模型 ...

并在没有DDD的情况下以更传统的方式开始实现您的服务,只使用存储库模式。当你开始感到自信时,也许你可以在聚合方面进入DDD,然后再转向ES。您可以随时更改初始服务。

我的建议是不要试图一次性完成,因为你会失败;我以前见过很多次。

  

例如:等待OrderShipped和OrderReceived events => fire CancelOrderCommand等待OrderCancelled =>消防订单已取消电子邮件

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

  

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

您希望在阅读模型中有哪些其他背景?

  

基础设施服务

Deals with infrastructure: db access, send emails, message queues, etc

不确定你的意思,但肯定看起来不对。消息队列或数据库服务??