CQRS 概述






4.21/5 (8投票s)
从宏观层面理解 CQRS 模式
引言
应用程序通常对读写操作使用相同的数据模型和数据库。这对大多数应用程序来说是理想的。然而,在许多情况下,应用程序具有不同的读写需求。
什么是CQRS
在设计任何应用程序时,我们都会创建与我们问题领域中的实体相对应的领域模型。这些通常是包含业务和验证逻辑的类。此外,在关系数据库的情况下,这些可能对应于数据库实体。
传统上,应用程序对读写操作使用相同的数据模型。这是一种很好的方法,因为它不会产生任何额外的复杂性,因为我们在应用程序中使用的是单个数据库和相应的模型。
但是,在许多情况下,这种方法可能并不理想。例如,当读取操作的数量与写入操作的数量大相径庭时。例如,如果应用程序具有大量的读取操作,例如航班预订 API。
这在大多数情况下都能很好地工作,但取决于需求,可能存在以下缺点:
如果应用程序的读取需求与写入需求不同
- 如果用于更新的 DTO 和模型对象中使用的逻辑存在显著差异。例如,如果更新操作具有复杂的验证要求,或者 DTO 需要从各种不同的数据源获取数据。
- 如果应用程序的读取操作可能需要比更新操作进行更大的扩展。这可能出现在查看请求数量远大于更新模型数量的情况下。
CQRS中命令和查询的作用
假设我们有一个电子商务应用程序。此应用程序允许用户查看和购买威慑产品。现在,设计此类应用程序的一种方法是使用相同的数据模型来进行读写操作(创建、更新和删除)。
考虑一下此应用程序的这种情况。它运行良好,并满足业务需求。一段时间后,许多用户对浏览产品更感兴趣,而不是实际购买产品。另一个问题是,由于我们对读写操作使用相同的数据模型,因此我们没有最佳地使用我们的模型。例如,即使模型仅用于查看目的,我们也需要在模型中实现安全性。
所以我们现在面临的两个重要问题是:
- 我们无法选择性地扩展应用程序的读写部分。
- 我们需要对读写操作实施相同的验证和模型优化。
CQRS是此类问题的理想解决方案。我们可以通过将应用程序职责分离为读写部分来重新设计此类应用程序。
CQRS代表命令查询责任隔离 (Command Query Responsibility Segregation),它用于将应用程序分离为读取端和写入端。
- C 代表命令 (Command),包含更新应用程序状态的操作。这些包括创建、更新和删除操作。
- Q 代表查询 (Query),包含返回应用程序状态的操作。这包括读取操作。
这种模式的主要优点之一是,由于它分离了读取和更新操作之间的责任,因此它使应用程序更加灵活。
有了这种理解,我们可以通过将应用程序职责分离为读取端和写入端来重新设计我们的应用程序。
我们现在通过实现 CQRS 解决了上述两个重要问题:
- 我们可以独立地扩展读取和写入模型。这取决于读取和写入模型的要求。
- 我们可以优化读取和写入模型。读取模型可以优化为使用诸如物化视图之类的对象来更快地检索数据,而不是使用多个表连接。类似地,可以单独优化写入模型。这导致更灵活的设计。
实现注意事项
为了实现这种模式,我们需要为命令和查询拥有单独的领域模型。
客户端将调用由命令处理程序执行的命令或查询。因此,命令处理程序或查询处理程序将接受命令或查询对象作为参数。
public interface ICommandHandler
{
bool HandleCommand(SampleComamnd);
}
public interface IQueryHandler
{
bool HandleQuery(SampleQuery);
}
我们不一定要对命令和查询使用不同的数据库。这取决于需求,例如,我们是否需要使用单独的数据库来物理地将读取和写入数据分开。如果我们使用不同的数据库,那么我们需要考虑如何使读取数据库与写入数据库保持同步。事件溯源是我们实现这一点的一种方法。
因此,在实现 CQRS 中的一个重要挑战是管理读取和写入数据库同步时的一致性。
为了理解这一点,我们可以考虑两种基本的一致性模型:
- 强一致性:更新一旦完成,就会在所有数据库副本中可用。
- 最终一致性:更改传播到副本需要一些延迟。
可扩展性和一致性之间总是存在一些权衡。在使用 CQRS 设计系统时,我们可以获得高可扩展性和灵活性。
对写入数据库所做的更新将传播到读取数据库。这样做可能需要一些延迟。但最终,读取数据库将使用这些更改进行更新。
历史
- 2023年9月20日:初始版本