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应用可能过度设计,建议根据实际项目复杂度选择。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播