【问题标题】:DDD, Event store, UIDDD、事件存储、用户界面
【发布时间】:2017-10-09 07:30:47
【问题描述】:

我有一个项目,它的设计或至少应该根据众所周知的 DDD 原则。

  1. 返回 - DDD + CQRS + 事件存储

  2. UI -ngrx/store

我有很多问题要问,但现在我会坚持这两个:

  1. 在执行单个命令/操作后应该如何更新 UI 存储?

a) 订阅 response.ok

b) 监听域事件

c) 触发保存创建/更新/删除对象的通用事件?

  1. 在每个命令/事件中传输整个聚合根 dto 及其所有实体是一个好主意,还是更好地使用更精细的命令/事件,例如:只有一个属性?

【问题讨论】:

    标签: c# domain-driven-design cqrs


    【解决方案1】:

    在执行单个命令/操作后应该如何更新 UI 存储?

    我的聚合中的命令方法返回 void(尊重 CQS);因此,接收命令请求的 REST 端点仅响应 OK, command is accepted 之类的内容。然后,这取决于后端服务器内部如何处理命令:

    • 如果命令是同步处理的,那么一个简单的OK, command is accepted 就足够了,因为 UI 会自行刷新并且新数据会在那里;
    • 如果命令是异步处理的,那么事情会变得更加复杂,并且应该返回某种命令 ID,所以像 OK, command is accepted and it has the ID 1234-abcd-5678-efgh; please check later at this URI for command completion status 这样的响应

    同时,您可以监听域事件。我使用从后端发送到 UI 的Server sent events 来执行此操作;如果 UI 是基于 Web 的,这将很有用,因为可能会打开多个浏览器窗口,并且页面的数据将在后台更新;很好,客户很满意。

    关于在命令响应中包含来自读取端的一些数据:这取决于您的具体情况;我避免使用它,因为它意味着在写入时读取,这意味着我无法将写入与更高级别的读取分开;我喜欢能够独立地从读取部分扩展写入。因此,response.ok 是最干净的解决方案。此外,这意味着命令/写入端点对调用者进行了一些查询假设;为什么命令处理程序/命令端点应该假设调用者需要什么数据?但也可能有例外,例如,如果您想减少请求的数量,或者如果您使用 API 网关,在命令发送到后端服务器后也执行 READ。

    在每个命令/事件中传输整个聚合根 dto 及其所有实体是一个好主意,还是更好地使用更精细的命令/事件,例如:只有一个属性?

    我在使用 CQRS 时从不发送整个聚合;您拥有读取模型,因此每个聚合在每个读取模型上都有不同的表示形式。因此,您应该为每个 UI 组件创建一个读取模型,这样您就只保留和发送显示在 UI 上的数据,而不是包含任何人都需要在任何地方显示的任何内容的类似上帝的对象。

    【讨论】:

    • 非常有用!谢谢!您是否使用任何类型的域事件存储实现,例如 EventStore ?
    • @IvelinMatev 是的,但我自己的一个; CQRS 库是 github.com/xprt64/cqrs-es,MongoDB 实现是 github.com/xprt64/cqrs-eventstore-mongodb两者都只适用于 PHP
    • 在 UI 上生成异步处理命令的 ID 是不是一个好主意?
    • @IvelinMatev 是的,它应该是一个 GUID;如果在发生故障时重新发送命令,您还可以使用该 ID 对 API 网关(如果有的话)上的命令进行重复数据删除。
    • 您是使用域事件来更新 UI,还是有单独的 EventStore 事件和单独的更新 UI?
    【解决方案2】:

    命令基本上属于以下两类之一:创建命令和其他命令。

    创建命令

    使用创建命令,您通常希望取回刚刚创建的东西的句柄,否则您将陷入黑暗,无处可去进一步操作它。

    • 我相信 CQS 和 CQRS 中的创建命令可以返回某种标识符或位置:请参阅 my answer here。该标识符可能会被可以在其响应中返回它的命令处理程序知道。这很好地映射到 REST 中的 201 Created + Location 标头。

    • 您还可以拥有客户端generate the ID。在这种情况下,请参见下文。

    所有其他命令

    客户端显然有对象的地址。在从 HTTP 部分获得 OK 后,它可以简单地重新查询其位置。或者,您可以轮询该位置,直到某事表明该命令成功。它可能是资源版本 ID、Constantin 指出的状态、Atom feed 等。

    还要注意,命令处理程序返回操作的成功状态可能更简单,这是否真的违反 CQS 是有争议的(再次,请参阅上面的答案)。

    【讨论】:

      【解决方案3】:

      将整个聚合根 dto 与所有 它在每个命令/事件中的实体,或者最好有更多 例如,粒度命令/事件:只有一个属性?

      确实,最好有细化的命令和事件。 命令和事件应该是不可变的、富有表现力的对象,可以清楚地表达意图或过去的业务事件。如果对象恰好包含即将更改或已更改的数据,则此方法效果最佳。

      【讨论】:

      • 好的,那么如果您有一个表单更新单个聚合根对象和根本身中的一堆实体 - 您是否应该为每个实体发送单独的命令?
      • 我不知道你的域,但提供类似 CRUD 的创建(也许)一个更新命令/事件可能是有意义的,每个都包含聚合根 DTO 的更大部分或所有内容.例如。如果您的 UI 包含一个大型 Create 表单,其中用户一次描述聚合根实例,在我看来,没有理由将传输拆分为多个命令。但这并不意味着每个命令都应该带有整个聚合根。你可能仍然会想出一堆非 CRUD 操作,它们做非常具体的事情并且应该只携带一些字段。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-10
      • 2017-04-03
      • 2019-10-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多