CQRS解构

栏目: 数据库 · 发布时间: 6年前

内容简介:本文讨论的是如何使用CQRS实现API设计。下面是名为Command / Query Responsibility Segregation(CQRS)的设计模式:

本文讨论的是如何使用CQRS实现API设计。

概述

下面是名为Command / Query Responsibility Segregation(CQRS)的设计模式:

     返回数据	做出改变
查询	:heavy_check_mark:	        :x:
命令	:x:    	:heavy_check_mark:

查询和命令是两种分离的API。

为何使用这种模式?我喜欢它有几个原因。作为API的消费者,我永远不必担心使用API出现异常了;相反,我能确切地知道哪些API调用会专门应付对系统的更改请求,没有含糊之处。这使得API 易于推理。

我曾经尝试创建一个统一的界面来完成两者,但是随着时间推移,出现“服务于两个主人“”的典型问题,单一界面变得更加混乱。

时间长了会发生:“我们不使用这个字段,为什么我们要更新它?。” 回应:“我不知道,但继续这样做或会出问题。”

:package:

因此,CQRS的核心是一个关注点分离的特定应用,即良好的组织实践。现在我们已经对模式进行了介绍,我将介绍一些实现细节和经验教训。

消息

我认为每个查询或命令都是一条消息。这意味着任何客户端系统都可以将这些表达为没有方法的普通数据(类或结构)。然后以线形格式(如JSON或CapnProto或w / e)轻松传输它们。

每条消息都有一个名称 - 通常只是类/结构名称 - 它在API中唯一标识它。如SearchCustomers(查询)或DeactivateCourse(命令)。名称用于标识请求的操作,然后将其与消息解析器和处理函数进行匹配。安全授权由此很简单,只要保留一个用户名单,允许这些用户可发送哪些消息名称;然后在处理任何用户的消息之前检查该列表。

运营

命令和查询应该如何工作似乎很明显。但是我发现了一些细微差别。

查询:

查询是很简单的:

1. API侦听 /query/[Query Name]

2. 验证用户是否具有[Query Name]权限

3. 反序列化查询消息

4. 将查询消息传递给其处理函数,该函数将:

5. 验证查询消息

6. 从数据库加载和转换数据

7. 序列化并返回数据

命令:

命令的目的是在系统上执行一些业务操作。在实践中,我们注意到命令是否需要更改一个或多个实体。出于架构原因,如何处理多个实体更改非常重要。

:pushpin: 在这种情况下,实体意味着某个逻辑单元。在高度规范化的表中,实体可能包含父对象和一对多关系的任何子对象。在DDD术语中,你可以将其称为聚合。在事件溯源中,这是一个事件流。

可扩展性

你可以在单个事务中执行多实体更改,以实现全有或全无的事务语义。这种方法很适合,但它限制了可伸缩性。要参与事务,所有受影响的实体必须位于同一数据库节点上。如果它们位于不同的节点上,则发生分布式事务(如果数据库支持)。随着负载的增加,分布式事务将逐渐变慢。跨实体事务是企业内部业务应用程序的有效方法,但对于公开的互联网服务,也许不是。

像互联网这种更加大规模级别应用,我们的方法是仅使用单实体命令进行更改。当用例需要更改多个实体时,请使用元命令(自身不做任何改变),而不是编排并运行单实体命令。我将单实体命令称为“基本命令”,将多实体命令称为“工作流程”。当工作流调用基本命令时,这些命令可能会失败,工作流程会以业务用例的方式处理故障。这可能意味着需要忽略失败并作为业务错误/警告返回给客户,或采取补偿回退措施。

客户端工作流

你可以在客户端实现工作流 - 让UI编排所有必要的基本命令。但是,我不在客户端实现工作流,而在API端实现工作流,主要理由是清晰(特别是安全性)。

我用一个真实例子来说明。我们有培训师的角色,这个角色不能创建课程,但是,他们可以记录他们提供给员工的培训。记录培训时可能需要创建一个有限选项的新课程。将记录培训作为API工作流执行,可以将其表示为单个细化权限:“培训师可以记录培训,但不能创建课程。” 在权限UI上选中一个选项,而不选中另一个选项。

如果采取客户端工作流,执行上述相同的操作,我们看看会怎样?我们需要添加一个基本命令:创建培训师课程,然后管理员用户必须被告知:“要给某人录制培训的权限,你必须检查Create Trainer Course并且Permission X和Permission Y。” 那么这就给最终用户造成的负担,需要他做这些流程。在这里,我们还可以创建一个伪命令,仅用于权限目的,映射到所需的基本命令。这就将负担又转移给开发人员。我不喜欢这些结果中的任何一个,所以我更喜欢API端工作流。

指导原则

在实施CQRS API时,人们会提出一些非常常见的问题。

1. 返回错误与返回数据不同。

一个流行的误解是命令应该什么都不返回。实际是命令应该返回一些东西。它们返回有关操作本身的元信息(无论是成功还是失败以及为什么。这与返回业务数据非常不同,后者是查询的工作。

2. 命令可以在不进行更改的情况下成功。

命令可以进行0次或更多次更改。换句话说,“进行更改”是命令的目的,而不是所需的结果。因此,对于成功运行的命令完全有效,但不会导致任何更改。

在运行命令之前和之后比较实体,如果它们完全相同,那么就是进行0更改并成功返回。

3. 命令处理代码可以调用查询。

这似乎违反了CQRS原则。命令的内部除了“进行更改”之外,它没有任何意思。

因此,可以在命令中运行查询,才能获取命令所需的一些信息,但是要小心一点。

4. 自动递增ID不应是主键ID。

常常需要返回自动生成的ID,因为自动增量ID非常方便,但是也有问题:重复。

场景:用户填写表单用来创建新实体并点击提交。请求超时。

自动增量冒险:如果自动增量字段是您唯一的ID,则你的应用无法知道请求是否成功。对这种情况的补救措施通常取决于用户的意识和参与。

如果用户再次点击提交(非常可能),但是先前的请求如果确实创建了实体,尽管超时,那么现在有两个具有不同ID的相同实体。要正确清理,用户现在应该搜索重复项并删除冗余实体​​(极不可能)。

或者,在超时之后,用户可以搜索他们可能创建的实体。如果他们找不到,请再回来填写表单。根据我的经验,这种情况不太可能。如果培训用户习惯于这样思考,则可能会发生这种情况。

还可以添加重复检查的外部系统,例如保持对已查看操作及其结果的记忆。但有更好的方法......

预先生成的ID(冒险):

在用户甚至开始键入任何内容之前,在加载表单时就生成(或从服务器请求)ID。

在用户被告知请求超时后,她再次点击提交。界面就会使用相同的预生成ID发送与之前相同的完全相同的请求。如果数据库没有重复会成功,否则API响应:“此实体已存在。” 如果UI可以识别出这个特定错误,它可以假装它正常成功。这种冒险带来了更好的用户体验,没有重复的机会。

我们的策略:我们倾向于使用UUID进行所有标识。它们很容易在许多平台上生成。他们无视趋势分析。我们的大多数创建表单都必须运行查询(例如,获取下拉列表数据),因此我们会在查询结果中包含一个新的UUID。

结论

命令是变动的守门人。查询则是知识库,这是CQRS。我发现这种模式使我走向正确的方向。它也是一种多功能的模式。它不关心你的部署的是单体还是微服务。甚至可以将命令和查询拆分为各自独立的服务,实现读写负载分离。

但请记住,这只是一个大系统中的一个部分,而不是适合每个系统的通用工具。CQRS模式在后端系统的边界内能很好地工作,与客户端应用程序连接。与任何模式一样,只有在适当的情况下应用它时才有用。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning PHP, MySQL, and JavaScript

Learning PHP, MySQL, and JavaScript

Robin Nixon / O'Reilly Media / 2009-7-21 / USD 39.99

Learn how to create responsive, data-driven websites with PHP, MySQL, and JavaScript - whether or not you know how to program. This simple, streamlined guide explains how the powerful combination of P......一起来看看 《Learning PHP, MySQL, and JavaScript》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具