第一次接触CQRS(Command Query Responsibility Segregation)是在处理一个电商平台的订单系统时。当时我们的单体架构遇到了性能瓶颈:同一个数据模型既要处理高频的订单状态查询,又要处理复杂的订单创建流程。这种读写耦合的设计让我们在流量高峰时苦不堪言——一个复杂的写操作会阻塞整个系统的查询性能。这时候CQRS就像一剂良药,通过读写分离彻底解决了我们的痛点。
CQRS不是银弹,但在特定场景下能带来显著收益。当你的系统出现以下特征时,就值得考虑采用CQRS:
PHP生态虽然以快速开发见长,但通过合理的架构设计,同样能实现优雅的CQRS方案。接下来我将分享在Laravel项目中落地CQRS的完整实践。
典型的PHP CQRS实现包含以下核心层次:
code复制├── Application
│ ├── Command
│ │ ├── CreateOrderCommand.php
│ │ └── CancelOrderCommand.php
│ └── Query
│ ├── OrderListQuery.php
│ └── OrderDetailQuery.php
├── Domain
│ ├── Model
│ ├── Repository
│ └── Event
├── Infrastructure
│ ├── CommandHandler
│ ├── QueryHandler
│ └── EventListener
└── UI
├── Http
└── Console
这种分层的关键在于:
在PHP中实现存储分离有多种方案:
方案A:单库多表
php复制// 写模型
class Order extends Model {
// 使用主库连接
protected $connection = 'mysql';
}
// 读模型
class OrderRead extends Model {
// 使用从库连接
protected $connection = 'mysql_read';
}
方案B:事件溯源+投影
php复制// 事件存储
class OrderCreatedEvent {
public function handle() {
EventStore::persist($this);
}
}
// 投影处理器
class OrderProjector {
public function onOrderCreated($event) {
OrderRead::create($event->payload);
}
}
方案C:消息队列同步
php复制// 命令执行后发布事件
class CreateOrderHandler {
public function handle($command) {
$order = Order::create(...);
event(new OrderCreated($order));
return $order;
}
}
// 监听事件更新读模型
class OrderEventSubscriber {
public function handleOrderCreated($event) {
Queue::push(new SyncOrderReadModel($event->order));
}
}
提示:中小型项目从方案A开始即可,当读写性能差异超过10倍时再考虑方案B/C
Laravel默认没有CQRS支持,但可以通过简单的扩展实现:
php复制// 注册命令总线
$this->app->singleton(CommandBus::class, function() {
return new CommandBus([
CreateOrderCommand::class => CreateOrderHandler::class
]);
});
// 命令基类
abstract class Command {
public function handle() {
return app(CommandBus::class)->execute($this);
}
}
// 使用示例
class OrderController {
public function store(CreateOrderRequest $request) {
$command = new CreateOrderCommand(
$request->input('items'),
$request->user()->id
);
return $command->handle();
}
}
读模型不需要遵循领域驱动设计,可以针对查询场景高度优化:
php复制class OrderQuery {
public function getActiveOrders(int $userId) {
return DB::connection('read')
->table('order_read')
->select('id', 'total', 'status')
->where('user_id', $userId)
->whereIn('status', ['paid', 'shipped'])
->orderBy('created_at', 'desc')
->paginate(15);
}
}
关键优化点:
CQRS的最大挑战是如何保证读写模型的一致性。在PHP中可以通过以下方式缓解:
php复制// 使用数据库事务
DB::transaction(function() use ($command) {
$order = Order::create(...);
event(new OrderCreated($order));
// 同步更新读模型
OrderRead::create([
'id' => $order->id,
'user_id' => $order->user_id,
'total' => $order->total
]);
});
// 或者使用延迟队列
event(new OrderCreated($order))->delay(now()->addSeconds(5));
我们在用户服务中对比了传统CRUD与CQRS模式的性能:
| 场景 | QPS | 平均响应时间 | 99线 |
|---|---|---|---|
| 传统模式创建订单 | 120 | 85ms | 210ms |
| CQRS模式创建订单 | 150 | 62ms | 145ms |
| 传统模式查询订单 | 800 | 25ms | 110ms |
| CQRS模式查询订单 | 3500 | 8ms | 35ms |
性能提升主要来自:
在以下情况引入CQRS可能适得其反:
问题1:读模型延迟
php artisan queue:monitor问题2:命令执行慢
问题3:数据不一致
在开发环境可以添加全局中间件记录所有命令和查询:
php复制class LogCqrsMiddleware {
public function handle($message, $next) {
$start = microtime(true);
$result = $next($message);
$time = (microtime(true) - $start) * 1000;
Log::debug(get_class($message), [
'time' => $time.'ms',
'data' => $message->toArray()
]);
return $result;
}
}
对于大型PHP应用,可以进一步考虑:
引入Event Sourcing:
读写分离进阶:
CQRS微服务化:
我在实际项目中发现,CQRS最大的价值不在于性能提升,而是它强制开发者将读写关注点分离。这种分离让系统更容易应对变化——当需要修改查询逻辑时,你完全不必担心会影响核心业务逻辑。