服务器架构
对应的官方页面地址
CQRS (ADR-0008)
我们的服务器架构使用命令和查询职责分离 (Command and Query Responsibility Segregation - CQRS) 的模式。
此模式的主要目标是分解专注于单个实体的大型服务(例如 CipherService
),并转向基于操作或任务的更小、可重用的类(例如 CreateCipher
)。将来,这可能会带来其他好处,例如将命令排队执行,但目前的重点是拥有更小的、可重用的代码块。
命令与查询
命令是写入操作,例如 RotateOrganizationApiKeyCommand
。他们永远不应该从数据库中读取数据。
查询是读取操作,例如 GetOrganizationApiKeyQuery
。他们永远不应该写入数据到数据库。
数据库是我们处理的最常见的数据源,但其他数据源也是可能的。例如,查询还可以从远程服务器获取数据。
每个查询或命令应该有一个单一的职责。例如:删除用户、获取许可证文件、轮换 API 密钥。它们是围绕动词或动作(例如 RotateOrganizationApiKeyCommand
)而不是域名或实体(例如 ApiKeyService
)设计的。
编写命令或查询
一个简单的查询可能只是一个从数据库获取数据的存储库调用。(我们已经使用存储库,这不是我们在这里关心的)但是,更复杂的查询可能需要围绕存储库调用的附加逻辑,这将需要它们自己的类。命令总是需要自己的类。
类、接口和公共方法应以操作命名。例如:
查询/命令应该只公开运行完整操作的公共方法。它不应该有公共助手方法。
目录结构和命名空间应按功能组织。接口应存储在单独的子文件夹中。例如:
维护命令/查询的卓越性
通过分离读写操作,CQRS 鼓励我们保持类之间的松散耦合。在我们的代码库中使用 CQRS 时,需要遵循两条黄金法则:
命令永远不应该读取,查询永远不应该写入
命令和查询不应该互相调用
这两者都会导致类之间的紧密耦合,减少代码重用的机会,并将命令/查询的区别混为一谈。
通常可以通过以下方式避免这些问题:
编写命令,以便它们在参数中接收所需的所有数据,而不是自己获取数据
按顺序调用查询和命令(一个接一个),沿调用链传递结果
例如,如果我们需要更新组织的 API 密钥,可能会倾向于使用 UpdateApiKeyCommand
来获取当前 API 密钥然后更新它。但是,我们可以将其分解为两个单独的查询/命令,分别调用:
这也有单元测试的好处 - 您可以简单地使用 Autodata
属性提供不同的参数值,而不是在模拟查询结果时进行冗长的「编排」阶段。
在实际情况下,您的命令和查询应该获取并返回整个对象(例如 User
)而不是单个属性(例如 userId
)。
避免过多的可选参数
过多的可选参数很快就会变得难以使用。相反,请考虑使用方法重载来为命令或查询提供不同的入口点。
最后更新于