PHP项目命令与查询分离

wen PHP项目 2

PHP项目命令与查询分离(CQRS)实践指南

基本概念

命令(Command): 改变系统状态的操作(写)

PHP项目命令与查询分离

  • 不返回数据(或只返回成功/失败)
  • 命名通常是动词短语: 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);
    }
}

最佳实践

  1. 命名规范: 命令用动词+名词,查询用名词
  2. 参数验证: 命令中做验证,查询只负责查询
  3. 单一职责: 一个命令/查询只做一件事
  4. 类型安全: 使用强类型参数
  5. 幂等性: 命令应该是幂等的
  6. 日志记录: 记录命令执行的重要步骤

性能优化

<?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应用可能过度设计,建议根据实际项目复杂度选择。

抱歉,评论功能暂时关闭!