为什么当项目增长时,普通的CRUD会失效
想象一下:你正在用PHP编写一个在线商店。一开始一切都很简单——添加商品、修改价格、删除商品。但半年过去了,新的需求出现了:所有变更的审计、按特定日期恢复数据、复杂的折扣逻辑。普通的CRUD(创建、读取、更新、删除)开始捉襟见肘。数据库变得一团糟,而客户的每一个新请求都要求重写一半的系统。
熟悉吗?那么你一定会需要CQRS(命令查询职责分离)和事件溯源——这两个强大的模式改变了开发方式。在本文中,我们将通过真实的PHP示例来解析它们,以便你可以在自己的项目中应用它们。
用简单的话说,什么是CQRS和事件溯源
CQRS:命令与查询的分离
CQRS是一种架构模式,它将操作分为两种类型:命令(改变状态)和查询(读取状态)。在经典的MVC中,控制器和模型都处理相同的实体。CQRS建议将这些职责分开。
想象一个图书馆:有一个专门用于登记书籍的桌子(命令),另一个专门用于阅读的桌子(查询)。它们互不干扰,并且各自针对自己的任务进行了优化。
事件溯源:变更历史而非当前状态
事件溯源是一种数据存储方式,你保存的不是对象的当前状态,而是导致该状态的一系列事件。不是存储一行“订单#123:状态 = 已送达”,而是存储:“订单已创建 → 已付款 → 已发货 → 已送达”。
这就像视频记录而不是照片:你总是可以回放,查看任何时刻发生了什么。
何时在PHP中应用CQRS和事件溯源
这些模式并非银弹。对于简单的博客来说,它们过于复杂。但以下场景中,它们不可或缺:
- 复杂的领域逻辑(金融、预订、物流)
- 审计要求——需要知道谁在何时更改了数据
- 按任意日期恢复状态
- 微服务架构——事件可以轻松在服务间传递
- 高读取负载——可以创建独立的、针对查询优化的读取模型
如果你的项目是一个拥有数千个订单的在线商店、CRM系统或银行应用程序——CQRS和事件溯源绝对会派上用场。
在PHP中实践CQRS的实现
让我们进入代码部分。我们将实现一个简单的订单管理系统。先从CQRS开始。
步骤1:定义命令和查询
命令是一个DTO(数据传输对象),包含执行操作所需的所有数据。命令从不返回数据——只改变状态。
<?php// src/Application/Command/CreateOrderCommand.phpnamespace App\\Application\\Command;
class CreateOrderCommand{ public function __construct( public readonly string $orderId, public readonly string $customerId, public readonly array $items, // [['productId' => '...', 'quantity' => 2], ...] public readonly \\DateTimeImmutable $createdAt ) {}}查询也是一个DTO,但用于读取。它可以包含过滤器并返回数据。
<?php// src/Application/Query/GetOrderQuery.phpnamespace App\\Application\\Query;
class GetOrderQuery{ public function __construct( public readonly string $orderId ) {}}步骤2:创建命令处理器
命令处理器是一个执行业务逻辑的服务。它接收命令并改变状态