带有 REST API 的 CQRS

Posted

技术标签:

【中文标题】带有 REST API 的 CQRS【英文标题】:CQRS with REST APIs 【发布时间】:2020-07-09 19:50:50 【问题描述】:

我正在使用 EventSourcing 在 CQRS 上构建一个 REST 服务,以跨服务分发对我的域的更改。我启动并运行了 REST 服务,使用 POST 端点创建初始模型,然后使用一系列 PATCH 端点来更改模型。每个端点都有一个与之关联的命令,客户端将其作为Content-Type 参数发送。例如,Content-Type=application/json;domain-command=create-project。我有以下用于在我的任务/项目管理服务上创建项目记录的端点。

api.foo.com/project 动词: POST 命令: 创建项目 作用:在事件存储中插入一个新模型并设置一些默认值 api.foo.com/project/projectId 动词:补丁 命令:重命名项目 作用:project-renamed 事件插入到具有新项目名称的事件存储中。 api.foo.com/project/projectId 动词:补丁 命令:重新安排项目 它的作用:project-rescheduled 事件插入到带有新项目截止日期的事件存储中。 api.foo.com/project/projectId 动词:补丁 命令: set-project-status 作用:project-status-changed 事件插入到具有新项目状态(活动、计划、存档等)的事件存储中。 api.foo.com/project/projectId 动词:删除 命令: 删除项目 作用:project-deleted 事件插入到事件存储中

传统上,在 REST 服务中,您会提供一个 PUT 端点,以便可以替换记录。我不确定这在事件溯源 + CQRS 模式中是如何工作的。我会只使用 POST 和 PATCH 动词吗?

我担心我是细化的,并且每个字段都不需要与之关联的命令。 PUT 端点可用于替换片段。不过我担心的是事件存储会不同步,所以我只是坚持使用 PATCH 端点。这种粒度级别是典型的吗?对于带有6 properties on it 的模型,我有5 commands 来调整模型的属性。

【问题讨论】:

【参考方案1】:

这是我们在帮助开发人员开始使用 CQRS/ES 时经常遇到的一个常见问题。我们需要承认,以纯粹的方式应用 REST 与 DDD/CQRS 非常不匹配,因为命令的意图没有在动词 GET/POST/PUT/PATCH/DELETE 中明确表达(即使您可以 像你一样使用content-type)。此外,系统的 C/R 端肯定是与 REST 不匹配的 CQRS 系统中的不同资源。

但是,使用 HTTP 为 CQRS/ES 系统提供 API 是非常实用的。

我们通常只使用POST 发送命令到/commands 端点或带有命令名称的端点,即/commands/create-project。这完全取决于你想变得多么严格。在这种情况下,我们将命令类型嵌入到负载中或作为内容类型。

但是,这完全取决于什么与技术堆栈更匹配,而您在此处选择的内容通常不会决定解决方案的成败。更重要的部分通常是创建一个好的领域模型并让整个团队都采用这种思维方式。

祝你好运!

【讨论】:

【参考方案2】:

想到的一个问题是,REST 是否是 CQRS 的正确范例?

一种完全不同的构建方式是不使用以操作为中心的端点,而是将 REST API 构建为一系列事件,您可以向其中添加新事件(使用 POST)。

事件应该是不可变的并且只能追加,所以DELETE 方法可能对突变没有多大意义。

如果您完全使用 CQRS(祝您好运,我听说过战争故事),我会倾向于构建一个能够很好地反映该模型的 API。

【讨论】:

我使用 CQRS 的唯一原因是因为它似乎与事件溯源搭配得很好。我正在使用事件溯源来保持所有解耦的无服务器微服务同步。是否有不同的模式或示例/文档,您知道我可以将事件溯源与 REST 一起使用,而将 CQRS 全部排除在外吗?我的产品本身并不真正需要 CQRS,我可能把它复杂化了。 不,我认为 CQRS 和事件溯源都是主要的危险信号,除非你的用例是在分类账上很好建模的东西,例如会计系统、版本控制等。这太离谱了主题虽然=)。我已经构建了事件源系统,但这些系统是用于股票/期权交易分类帐的,因此非常适合。我的理解是,对于大多数其他应用程序,开发人员最终会为此感到遗憾。如果这是您第一次听到这个,我建议您进行更多挖掘。【参考方案3】:

我只能使用 POST 和 PATCH 动词吗?

大多数时候,您会使用 POST。

PUT 和 PATCH 是使用远程创作语义定义的——它们是用于将资源的新表示从客户端复制到服务器的方法。例如,客户端GETs 是/project/12345 的表示,进行本地编辑,然后使用PUT 请求服务器接受客户端对资源的新表示作为自己的。

PATCH 从语义上讲,是一种类似的消息交换——不同之处在于,客户端不是发送资源的完整表示,而是返回一个“补丁文档”,服务器可以将其应用于其副本以进行更改。

现在,从技术上讲,PATCH 文档确实对什么是“补丁文档”进行了任何限制。然而,为了使 PATCH 比 POST 更有用,我们需要通用且被广泛认可的补丁文档格式(例如,application/merge-patch+jsonapplication/json-patch+json)。

这并不是您在此处真正使用的用例,您在其中定义特定于您的域的命令消息。

此外,远程创作语义与“域建模”(这是 CQRS 遗产的一部分)不太吻合。当我们对域进行建模时,我们通常赋予域模型决定如何将新信息与服务器已经知道的信息集成的权限。 PUT 和 PATCH 语义更像是您用来将信息写入贫血数据存储的内容。

另一方面,it is okay to use POST

POST 在 HTTP 中有许多有用的用途,包括“此操作不值得标准化”的一般用途。 ——菲尔丁,2009 年

回忆一下 REST 是万维网的架构风格可能会有所帮助,而 html 支持的唯一不安全的方法是 POST。

所以用 POST 替换你的 PATCH 命令,你就走在了正确的道路上。

Fielding, 2008

我还应该注意,以上内容还不是完全 RESTful,至少我是如何使用这个术语的。我所做的只是描述了服务接口,它只不过是任何 RPC。为了使它成为 RESTful,我需要添加超文本来介绍和定义服务,描述如何使用表单和/或链接模板执行映射,并提供代码以有用的方式组合可视化。我什至可以更进一步,将这些关系定义为一个标准,就像 Atom 已经标准化了一组具有预期语义的普通 HTTP 关系

这里也是如此 - 我们还没有达到“REST”,但我们通过选择更符合我们预期语义的标准化方法来改进事情。

最后一点——您可能也应该用 POST 替换您对 DELETE 的使用。 DELETE 可能是一个问题,原因有两个 - 语义不是您想要的,标准删除有效负载具有 no defined semantics

另一种表达方式:DELETE 来自transferring documents over a network 域,而不是来自您的 域。发送到您的资源的 DELETE 消息应该被理解为与发送到任何其他资源的 DELETE 消息的含义相同。这就是统一的接口约束在起作用:我们都同意 HTTP 方法标记在任何地方都意味着相同的东西。

相对较少的资源允许使用 DELETE 方法 -- 它的主要用途是远程创作环境,用户对其效果有一定的指导 -- RFC 7231

和以前一样:远程创作语义显然不适合将消息发送到域模型。

【讨论】:

谢谢 - 这真的很丰富。我想我会回到非 CQRS 实现来简化事情。我的域名没那么复杂。但是对于微服务架构,您是否仍会使用某种形式的事件溯源或通知来避免微服务一直相互调用以获取它们可能需要的数据?例如,任务应用程序需要在删除用户时删除任务和笔记记录。我的帐户服务会 ping 我的 API 侦听的 webhook - 还是我只是将事件发布到在单个服务中处理通知的无服务器函数? 中心思想是两个微服务应该能够通信,即使它们不同时运行。常见的解决方案是让他们通过从高可用性消息存储中写入/读取消息来进行通信。【参考方案4】:

这篇 Google Cloud 文章 API design: Understanding gRPC, OpenAPI and REST and when to use them 阐明了 REST 与 RPC 的争论。 REST 与以实体为中心的 API 更相关,而 RPC 与以动作为中心的 API(和 CQRS)更相关。带有超媒体控件的最成熟的REST level 3 仅适用于具有简单状态模型的实体。

首先了解并评估 REST 对您的案例的好处。许多 API 是 REST-ish 而不是 RESTful。 OpenAPI 实际上是 RPC 映射和 HTTP 端点,但这并不妨碍它被广泛采用。

【讨论】:

以上是关于带有 REST API 的 CQRS的主要内容,如果未能解决你的问题,请参考以下文章

带有 REST API 的 CQRS

javascript 带有REST API调用的Html表

带有 eppn 的 REST-API users.list

带有付款的 PayPal REST API 交叉参考交易

带有图像处理的 REST API?

带有 jwt 身份验证的 django rest api 要求 csrf 令牌