带有 REST API 的 CQRS
CQRS with REST APIs
我正在使用 EventSourcing 通过 CQRS 构建 REST 服务,以跨服务分发对我的域的更改。我启动了 REST 服务并 运行,其中有一个 POST 端点用于创建初始模型,然后是一系列 PATCH 端点来更改模型。每个端点都有一个与之关联的命令,客户端将其作为 Content-Type
参数发送。例如,Content-Type=application/json;domain-command=create-project
。我有以下端点用于在我的 task/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}
- 动词:补丁
- 命令: 设置项目状态
- 它的作用: 将一个
project-status-changed
事件插入到具有新项目状态(活动、计划、存档等)的事件存储中。
- api.foo.com/project/{projectId}
- 动词:删除
- 命令:删除项目
- 它的作用: 将
project-deleted
事件插入事件存储
传统上,在 REST 服务中,您会提供一个 PUT 端点,以便可以替换记录。我不确定它在事件溯源 + CQRS 模式中是如何工作的。我会只使用 POST 和 PATCH 动词吗?
我担心自己过于细化,每个字段都不需要与之关联的命令。 PUT 端点可用于替换片段。不过,我担心的是事件存储会不同步,所以我只是坚持使用 PATCH 端点。这种粒度级别是典型的吗?对于6 properties on it I have 5 commands的模型,调整模型的属性。
我想到的一个问题是,REST 是 CQRS 的正确范例吗?
一种完全不同的构建方式是不使用以操作为中心的端点,而是将 REST API 构建为一系列事件,您可以向其中添加新事件(使用 POST) .
事件应该是不可变的并且只能追加,所以也许 DELETE
方法对于突变没有多大意义。
如果您全力以赴使用 CQRS(祝您好运,我听说过 war 故事),我会倾向于构建一个很好地反映该模型的 API。
Would I only ever use POST and PATCH verbs?
大多数时候,您会使用 POST。
PUT 和 PATCH 是使用远程创作语义定义的 - 它们是用于将资源的新表示形式从客户端复制到服务器的方法。例如,客户端 GET
是 /project/12345
的表示,进行本地编辑,然后使用 PUT
请求服务器接受客户端的新资源表示作为自己的。
从语义上讲,PATCH 是一种类似的消息交换 - 不同之处在于,客户端不是发送资源的完整表示,而是 returns 服务器可以应用到它的“补丁文档”复制以进行更改。
现在,从技术上讲,PATCH 文档确实对什么是“补丁文档”做了任何限制。然而,为了使 PATCH 比 POST 更有用,我们需要通用且被广泛认可的补丁文档格式(例如,application/merge-patch+json
或 application/json-patch+json
)。
这实际上不是您在这里的用例,您在其中定义特定于您的域的命令消息。
此外,远程创作语义与“域建模”(它是 CQRS 的一部分)不太一致。当我们对域建模时,我们通常授予域模型决定如何将新信息与服务器已知信息集成的权限。 PUT 和 PATCH 语义更像是您用来将信息写入贫血数据存储的语义。
POST serves many useful purposes in HTTP, including the general purpose of “this action isn’t worth standardizing.” -- Fielding, 2009
可能有助于回忆一下 REST 是万维网的架构风格,html 支持的唯一不安全方法是 POST。
因此,将您的 PATCH 命令替换为 POST,您就走在了正确的道路上。
I should also note that the above is not yet fully RESTful, at least how I use the term. All I have done is described the service interfaces, which is no more than any RPC. In order to make it RESTful, I would need to add hypertext to introduce and define the service, describe how to perform the mapping using forms and/or link templates, and provide code to combine the visualizations in useful ways. I could even go further and define these relationships as a standard, much like Atom has standardized a normal set of HTTP relationships with expected semantics
此处同样如此 - 我们还没有处于“REST”状态,但我们通过选择更符合我们预期语义的标准化方法改进了一些东西。
最后一点——您可能还应该将对 DELETE 的使用替换为 POST。 DELETE 可能是一个问题,原因有两个——语义不是你想要的,标准删除有效负载有 no defined semantics
换句话说:DELETE 来自 transferring documents over a network 域,而不是来自 您的 域。发送到您的资源的 DELETE 消息应该被理解为与发送到任何其他资源的 DELETE 消息相同。这就是工作中的统一接口约束:我们都同意 HTTP 方法标记在任何地方.
都意味着相同的事情
Relatively few resources allow the DELETE method -- its primary use is for remote authoring environments, where the user has some direction regarding its effect -- RFC 7231
和以前一样:远程创作语义显然不适合将消息发送到域模型。
这是一个常见问题,我们在帮助开发人员开始使用 CQRS/ES 时经常遇到这个问题。我们需要承认,以纯粹的方式应用 REST 与 DDD/CQRS 非常不匹配,因为命令的意图没有在动词 GET/POST/PUT/PATCH/DELETE 中明确表达(即使您 可以 像你一样使用 content-type
)。此外,系统的 C/R-side 肯定是 CQRS 系统中与 REST 不匹配的不同资源。
但是,使用 HTTP 为 CQRS/ES 系统提供 API 是非常实用的。
我们通常只使用 POST
发送命令,发送到 /commands
端点或带有命令名称的端点,即 /commands/create-project
。这完全取决于您想变得多么严格。在这种情况下,我们将命令类型嵌入到有效负载中或作为内容类型。
但是,这完全取决于什么与技术堆栈更匹配,而您在此处选择的内容通常不会决定解决方案的成败。更重要的部分通常是创建一个好的领域模型并让整个团队都采用这种思维方式。
祝你好运!
这篇 Google 带有超媒体控件的云文章 API design: Understanding gRPC, OpenAPI and REST and when to use them clarifies the REST vs RPC debate. REST is more relevant for entity-centric API whereas RPC is more relevant for action-centric API (and CQRS). The most mature REST level 3 仅适用于具有简单状态模型的实体。
首先了解并评估 REST 对您的案例的好处。许多 API 是 REST-ish 而不是 RESTful。 OpenAPI 实际上是 RPC 映射和 HTTP 端点,但这并不妨碍它被广泛采用。
我正在使用 EventSourcing 通过 CQRS 构建 REST 服务,以跨服务分发对我的域的更改。我启动了 REST 服务并 运行,其中有一个 POST 端点用于创建初始模型,然后是一系列 PATCH 端点来更改模型。每个端点都有一个与之关联的命令,客户端将其作为 Content-Type
参数发送。例如,Content-Type=application/json;domain-command=create-project
。我有以下端点用于在我的 task/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}
- 动词:补丁
- 命令: 设置项目状态
- 它的作用: 将一个
project-status-changed
事件插入到具有新项目状态(活动、计划、存档等)的事件存储中。
- api.foo.com/project/{projectId}
- 动词:删除
- 命令:删除项目
- 它的作用: 将
project-deleted
事件插入事件存储
传统上,在 REST 服务中,您会提供一个 PUT 端点,以便可以替换记录。我不确定它在事件溯源 + CQRS 模式中是如何工作的。我会只使用 POST 和 PATCH 动词吗?
我担心自己过于细化,每个字段都不需要与之关联的命令。 PUT 端点可用于替换片段。不过,我担心的是事件存储会不同步,所以我只是坚持使用 PATCH 端点。这种粒度级别是典型的吗?对于6 properties on it I have 5 commands的模型,调整模型的属性。
我想到的一个问题是,REST 是 CQRS 的正确范例吗?
一种完全不同的构建方式是不使用以操作为中心的端点,而是将 REST API 构建为一系列事件,您可以向其中添加新事件(使用 POST) .
事件应该是不可变的并且只能追加,所以也许 DELETE
方法对于突变没有多大意义。
如果您全力以赴使用 CQRS(祝您好运,我听说过 war 故事),我会倾向于构建一个很好地反映该模型的 API。
Would I only ever use POST and PATCH verbs?
大多数时候,您会使用 POST。
PUT 和 PATCH 是使用远程创作语义定义的 - 它们是用于将资源的新表示形式从客户端复制到服务器的方法。例如,客户端 GET
是 /project/12345
的表示,进行本地编辑,然后使用 PUT
请求服务器接受客户端的新资源表示作为自己的。
从语义上讲,PATCH 是一种类似的消息交换 - 不同之处在于,客户端不是发送资源的完整表示,而是 returns 服务器可以应用到它的“补丁文档”复制以进行更改。
现在,从技术上讲,PATCH 文档确实对什么是“补丁文档”做了任何限制。然而,为了使 PATCH 比 POST 更有用,我们需要通用且被广泛认可的补丁文档格式(例如,application/merge-patch+json
或 application/json-patch+json
)。
这实际上不是您在这里的用例,您在其中定义特定于您的域的命令消息。
此外,远程创作语义与“域建模”(它是 CQRS 的一部分)不太一致。当我们对域建模时,我们通常授予域模型决定如何将新信息与服务器已知信息集成的权限。 PUT 和 PATCH 语义更像是您用来将信息写入贫血数据存储的语义。
POST serves many useful purposes in HTTP, including the general purpose of “this action isn’t worth standardizing.” -- Fielding, 2009
可能有助于回忆一下 REST 是万维网的架构风格,html 支持的唯一不安全方法是 POST。
因此,将您的 PATCH 命令替换为 POST,您就走在了正确的道路上。
I should also note that the above is not yet fully RESTful, at least how I use the term. All I have done is described the service interfaces, which is no more than any RPC. In order to make it RESTful, I would need to add hypertext to introduce and define the service, describe how to perform the mapping using forms and/or link templates, and provide code to combine the visualizations in useful ways. I could even go further and define these relationships as a standard, much like Atom has standardized a normal set of HTTP relationships with expected semantics
此处同样如此 - 我们还没有处于“REST”状态,但我们通过选择更符合我们预期语义的标准化方法改进了一些东西。
最后一点——您可能还应该将对 DELETE 的使用替换为 POST。 DELETE 可能是一个问题,原因有两个——语义不是你想要的,标准删除有效负载有 no defined semantics
换句话说:DELETE 来自 transferring documents over a network 域,而不是来自 您的 域。发送到您的资源的 DELETE 消息应该被理解为与发送到任何其他资源的 DELETE 消息相同。这就是工作中的统一接口约束:我们都同意 HTTP 方法标记在任何地方.
都意味着相同的事情Relatively few resources allow the DELETE method -- its primary use is for remote authoring environments, where the user has some direction regarding its effect -- RFC 7231
和以前一样:远程创作语义显然不适合将消息发送到域模型。
这是一个常见问题,我们在帮助开发人员开始使用 CQRS/ES 时经常遇到这个问题。我们需要承认,以纯粹的方式应用 REST 与 DDD/CQRS 非常不匹配,因为命令的意图没有在动词 GET/POST/PUT/PATCH/DELETE 中明确表达(即使您 可以 像你一样使用 content-type
)。此外,系统的 C/R-side 肯定是 CQRS 系统中与 REST 不匹配的不同资源。
但是,使用 HTTP 为 CQRS/ES 系统提供 API 是非常实用的。
我们通常只使用 POST
发送命令,发送到 /commands
端点或带有命令名称的端点,即 /commands/create-project
。这完全取决于您想变得多么严格。在这种情况下,我们将命令类型嵌入到有效负载中或作为内容类型。
但是,这完全取决于什么与技术堆栈更匹配,而您在此处选择的内容通常不会决定解决方案的成败。更重要的部分通常是创建一个好的领域模型并让整个团队都采用这种思维方式。
祝你好运!
这篇 Google 带有超媒体控件的云文章 API design: Understanding gRPC, OpenAPI and REST and when to use them clarifies the REST vs RPC debate. REST is more relevant for entity-centric API whereas RPC is more relevant for action-centric API (and CQRS). The most mature REST level 3 仅适用于具有简单状态模型的实体。
首先了解并评估 REST 对您的案例的好处。许多 API 是 REST-ish 而不是 RESTful。 OpenAPI 实际上是 RPC 映射和 HTTP 端点,但这并不妨碍它被广泛采用。