DEV Community

架构师小白
架构师小白

Posted on

CQRS模式:命令查询职责分离的架构艺术

CQRS模式:命令查询职责分离的架构艺术

"读和写是不同的操作,为什么要用同一个模型来处理?"

在传统的CRUD应用中,我们通常使用同一个数据模型来handle读和写操作。然而,随着系统复杂度提升,这种方式会带来性能瓶颈、领域逻辑混乱等问题。CQRS(Command Query Responsibility Segregation) 模式应运而生,它将读操作和写操作分离,让系统更灵活、更高效。

什么是CQRS?

CQRS核心思想很简单:将读操作(Query)和写操作(Command)分开处理

  • Command(命令):修改系统状态的操作,如创建、更新、删除
  • Query(查询):只读取数据,不改变状态

传统的架构是:

用户 → CRUD Model → 数据库
Enter fullscreen mode Exit fullscreen mode

CQRS架构是:

用户 → Command Model → 数据库
   ↘ Query Model → 数据库
Enter fullscreen mode Exit fullscreen mode

CQRS的核心优势

1. 性能优化

读和写的性能需求往往不同:

  • 读操作可能需要复杂的联表查询、聚合
  • 写操作需要严格的业务验证

分离后,可以分别为读和写优化:

  • 读端:使用物化视图、缓存、反范式化
  • 写端:保持规范的领域模型

2. 领域模型清晰

写模型(Command Model)专注于业务逻辑,保持领域的纯粹性
读模型(Query Model)专为展示需求定制,不受业务规则污染

3. 可扩展性强

  • 读负载高?独立扩展读服务
  • 写负载高?独立扩展写服务
  • 无需互相影响

4. 支持复杂业务场景

  • 写操作需要严格的验证和业务流程
  • 读操作可能需要聚合多个数据源

CQRS实现模式

模式一:同步CQRS(最简单)

读写共用同一个数据库,但在应用层分离

模式二:事件驱动CQRS(推荐)

使用事件溯源(Event Sourcing),写操作产生事件,读端通过订阅事件更新物化视图

实战代码示例

定义Command和Query模型

# Command Model - 写模型
class CreateOrderCommand:
    def __init__(self, customer_id, items):
        self.customer_id = customer_id
        self.items = items

    def validate(self):
        if not self.items:
            raise ValueError("订单不能为空")
        return True

# Query Model - 读模型
class OrderDetailQuery:
    def __init__(self, order_id):
        self.order_id = order_id
Enter fullscreen mode Exit fullscreen mode

Command Handler

class OrderCommandHandler:
    def __init__(self, event_store):
        self.event_store = event_store

    def handle_create_order(self, command: CreateOrderCommand):
        command.validate()

        # 创建订单事件
        event = OrderCreatedEvent(
            customer_id=command.customer_id,
            items=command.items,
            timestamp=datetime.now()
        )

        self.event_store.append(event)
        return event.order_id
Enter fullscreen mode Exit fullscreen mode

Query Handler

class OrderQueryHandler:
    def __init__(self, read_database):
        self.read_db = read_database

    def get_order_detail(self, query: OrderDetailQuery):
        # 直接从物化视图读取,不需要复杂的聚合
        return self.read_db.orders.find_one(
            {"order_id": query.order_id}
        )

    def get_customer_orders(self, customer_id):
        # 为读取优化的查询
        return list(self.read_db.order_summary.find(
            {"customer_id": customer_id}
        ))
Enter fullscreen mode Exit fullscreen mode

CQRS的挑战与注意事项

1. 数据一致性

  • 读写分离后,数据同步需要时间(最终一致性)
  • 需要权衡:实时性要求高的场景慎用

2. 复杂度增加

  • 两套模型需要维护
  • 事件驱动模式需要处理事件幂等、补偿等

3. 不适用于所有场景

  • 简单CRUD应用不需要CQRS
  • 读写比例接近的系统收益不大
  • 团队经验不足时慎用

4. 建议结合DDD使用

CQRS与领域驱动设计天然契合:

  • Command对应Domain Command
  • Query端可以有自己的Read Model
  • 事件驱动是DDD常用的技术手段

什么时候使用CQRS?

✅ 推荐使用:

  • 复杂业务逻辑的企业应用
  • 读写负载差异明显的系统
  • 需要高性能读取的场景(如报表、仪表盘)
  • 基于DDD的微服务架构

❌ 不建议使用:

  • 简单的CRUD应用
  • 强一致性要求极高的场景
  • 团队对模式不熟悉

总结

CQRS不是银弹,但它为复杂系统提供了一种优雅的架构选择。通过将读和写分离,我们可以:

  • 获得更好的性能优化空间
  • 保持领域模型的清晰
  • 构建更具可扩展性的系统

记住:架构模式是为了解决问题,而不是为了使用而使用。在合适的场景下,CQRS会成为你构建高质量系统的有力工具。


本文是软件架构系列文章的一部分,如果你对DDD、六边形架构等话题感兴趣,欢迎关注!

Top comments (0)