PHP项目命令与查询分离(CQRS)实践指南
基本概念
命令(Command): 改变系统状态的操作(写)

- 不返回数据(或只返回成功/失败)
- 命名通常是动词短语:
CreateUser,PlaceOrder
查询(Query): 获取系统状态的操作(读)
- 返回数据
- 不改变系统状态
基础架构示例
目录结构
app/
├── Command/
│ ├── CreateUser/
│ │ ├── CreateUserCommand.php
│ │ └── CreateUserHandler.php
│ └── UpdateUser/
│ ├── UpdateUserCommand.php
│ └── UpdateUserHandler.php
├── Query/
│ ├── UserQuery/
│ │ ├── GetUserQuery.php
│ │ └── GetUserHandler.php
│ └── UserQueryHandler.php
├── Models/
│ └── User.php
└── Services/
└── BusService.php
命令实现示例
命令类
<?php
// app/Command/CreateUser/CreateUserCommand.php
class CreateUserCommand
{
public string $name;
public string $email;
public ?string $password;
public function __construct(string $name, string $email, ?string $password = null)
{
$this->name = $name;
$this->email = $email;
$this->password = $password;
}
}
命令处理器
<?php
// app/Command/CreateUser/CreateUserHandler.php
class CreateUserHandler
{
private UserRepository $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function handle(CreateUserCommand $command): void
{
// 验证
if (empty($command->name) || empty($command->email)) {
throw new \InvalidArgumentException('Name and email are required');
}
// 创建用户
$user = new User();
$user->name = $command->name;
$user->email = $command->email;
$user->password = $command->password ?? $this->generatePassword();
// 保存
$this->userRepository->save($user);
}
private function generatePassword(): string
{
return bin2hex(random_bytes(8));
}
}
查询实现示例
查询类
<?php
// app/Query/UserQuery/GetUserQuery.php
class GetUserQuery
{
public int $userId;
public function __construct(int $userId)
{
$this->userId = $userId;
}
}
查询处理器
<?php
// app/Query/UserQuery/GetUserHandler.php
class GetUserHandler
{
private UserRepository $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function handle(GetUserQuery $query): ?UserDto
{
$user = $this->userRepository->findById($query->userId);
if (!$user) {
return null;
}
return new UserDto(
id: $user->id,
name: $user->name,
email: $user->email,
createdAt: $user->createdAt
);
}
}
数据传输对象(DTO)
<?php
// app/Query/UserQuery/UserDto.php
class UserDto
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly string $email,
public readonly string $createdAt
) {}
}
命令/查询总线
<?php
// app/Services/BusService.php
class BusService
{
private array $handlers = [];
public function register(string $commandClass, callable $handler): void
{
$this->handlers[$commandClass] = $handler;
}
public function handle(object $command): mixed
{
$commandClass = get_class($command);
if (!isset($this->handlers[$commandClass])) {
throw new \RuntimeException("No handler registered for: $commandClass");
}
return $this->handlers[$commandClass]($command);
}
}
控制器调用
<?php
// app/Http/Controllers/UserController.php
class UserController extends Controller
{
private BusService $busService;
public function __construct(BusService $busService)
{
$this->busService = $busService;
}
public function store(CreateUserRequest $request): JsonResponse
{
try {
$command = new CreateUserCommand(
name: $request->name,
email: $request->email,
password: $request->password
);
$this->busService->handle($command);
return response()->json(['message' => 'User created successfully'], 201);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
}
public function show(int $id): JsonResponse
{
$query = new GetUserQuery($id);
$user = $this->busService->handle($query);
if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}
return response()->json($user);
}
}
高级模式: 使用消息队列
<?php
// 命令队列实现
class AsyncCommandBus
{
private QueueService $queueService;
private array $handlers = [];
public function dispatch(object $command): void
{
// 序列化命令到队列
$this->queueService->push([
'type' => get_class($command),
'data' => serialize($command)
]);
}
public function process(): void
{
while ($job = $this->queueService->pop()) {
$command = unserialize($job['data']);
$handler = $this->handlers[$command::class];
$handler->handle($command);
}
}
}
CQRS + Event Sourcing
<?php
// 事件驱动的CQRS
class EventSourcingHandler
{
private EventStore $eventStore;
private Projection $projection;
public function handle(object $command): void
{
// 1. 验证命令
$this->validate($command);
// 2. 创建事件
$event = match(get_class($command)) {
CreateUserCommand::class => new UserCreatedEvent(
$command->name,
$command->email
),
UpdateUserCommand::class => new UserUpdatedEvent(
$command->userId,
$command->changes
)
};
// 3. 保存事件
$this->eventStore->append($event);
// 4. 更新投影(读模型)
$this->projection->apply($event);
}
}
最佳实践
- 命名规范: 命令用动词+名词,查询用名词
- 参数验证: 命令中做验证,查询只负责查询
- 单一职责: 一个命令/查询只做一件事
- 类型安全: 使用强类型参数
- 幂等性: 命令应该是幂等的
- 日志记录: 记录命令执行的重要步骤
性能优化
<?php
// 查询缓存示例
class CachedUserQueryHandler
{
private Cache $cache;
private UserRepository $repository;
public function handle(GetUserQuery $query): ?UserDto
{
$cacheKey = "user:{$query->userId}";
return $this->cache->remember($cacheKey, 3600, function () use ($query) {
$user = $this->repository->findById($query->userId);
return $user ? new UserDto(...) : null;
});
}
}
CQRS特别适合复杂业务系统,能够清晰分离读写逻辑,提高系统的可维护性和可扩展性,对于简单CRUD应用可能过度设计,建议根据实际项目复杂度选择。