CQRS with REST APIs



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

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

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

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

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

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

我们通常只使用POST/commands端点或具有命令名称的端点(即/commands/create-project)发送命令。这一切都取决于你想要有多严格。在这种情况下,我们将命令类型嵌入到有效负载中或作为内容类型。

然而,这都是一个与技术堆栈更匹配的问题,你在这里选择的通常不会决定解决方案的成败。更重要的部分通常是创建一个好的领域模型,并让整个团队采用这种思维方式。

祝你好运!

脑海中浮现的一个问题是,REST是CQRS的正确范例吗?

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

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

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

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

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

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

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

现在,从技术上讲,PATCH文档确实对";补丁文档";然而,为了使PATCH比POST更有用,我们需要通用且广泛认可的补丁文档格式(例如,application/merge-patch+jsonapplication/json-patch+json)。

这并不是您真正拥有的用例,在这里您定义了特定于您的域的命令消息。

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

另一方面,可以使用POST

POST在HTTP中有许多有用的用途,包括"此操作不值得标准化"的一般用途。--Fielding,2009

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

因此,用POST替换PATCH命令,您就走上了正确的道路。

Fielding,2008

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

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

最后要注意的一点是,您可能也应该将DELETE的使用替换为POST。DELETE可能是一个问题,原因有两个——语义不是您想要的,标准的DELETE有效载荷没有定义的语义

换句话说:DELETE是通过网络域传输文档,而不是通过您的域。发送到您的资源的DELETE消息应被理解为与发送到任何其他资源的DELETE信息的含义相同。这就是工作中的统一接口约束:我们都同意HTTP方法令牌在任何地方都意味着相同的东西。

允许DELETE方法的资源相对较少——它的主要用途是用于远程创作环境,在那里用户可以了解它的效果——RFC 7231

如前所述:远程创作语义显然不适合向域模型发送消息。

这篇谷歌云文章API设计:理解gRPC、OpenAPI和REST以及何时使用它们阐明了REST与RPC的争论。REST与以实体为中心的API更相关,而RPC与以动作为中心的API(和CQRS)更相关。具有超媒体控件的最成熟的REST级别3仅适用于具有简单状态模型的实体。

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

最新更新