CQRS (Command and Query Responsibility Segregation) 是一种在现代软件开发中越来越普遍的架构模式。 它通过分离写操作(指令)和读操作(查询)来提高系统的可读性、可扩展性和可维护性。 最近,我尝试了一些CQRS实现,其中一个是使用PHP的 cqrs-example-php 。
这个例子实现了一个基本的事件存储库和CQRS框架。 它包括两个主要部分和一个示例实现。 一部分是用于定义事件、领域模型和存储库的核心框架。 另一部分是建立在核心框架上的一个示例应用,它演示了如何使用CQRS设计模式创建一个简单的任务列表应用。
让我们先看一下实现的基础框架。 首先,事件是最小化的信息单元,它代表了一些已经发生的领域事件。 事件通常有一个相关的名称和一组属性。 在cqrs-example-php中,事件可以通过实现 `CQRS\Event` 接口来自定义。
```php
interface Event {
public function getName(): string;
public function getPayload(): array;
public function occurredOn(): DateTimeImmutable;
}
```
事件存储库负责存储事件流和检索事件流。 在cqrs-example-php中,存储库由一个抽象类 `CQRS\EventStore\EventStore` 实现,并由不同的存储库驱动支持。 以下是存储库框架的主要组成部分:
```php
abstract class EventStore {
public function appendTo(UUID $aggregateUUID, EventStream $eventStream): void;
public function readAllEvents(): EventStream;
public function readEventsByAggregate(UUID $aggregateUUID): EventStream;
public function getAggregateHistory(UUID $aggregateUUID): AggregateHistory;
public function getLastEventUUID(): UUID;
}
class EventStream implements IteratorAggregate {
public function __construct(Event... $events);
}
class AggregateHistory implements IteratorAggregate {
public function __construct(UUID $aggregateUUID, EventStream $eventStream);
}
```
存储库驱动程序主要负责与特定的事件存储库集成。 cqrs-example-php 支持多个简单的事件存储库实现,包括内存事件存储库、Redis事件存储库和SQLite事件存储库。 如果你想尝试使用自己的事件存储库,只需要实现 `CQRS\EventStore\EventStore` 类即可。
```php
class InMemoryEventStore extends EventStore {
public function __construct();
}
class RedisEventStore extends EventStore {
public function __construct(string $dsn);
}
class SQLiteEventStore extends EventStore {
public function __construct(string $dsn);
}
```
现在,我们看看如何构建一个简单的任务列表应用程序。 首先,我们需要确定这个应用程序的主要目标。 在本例中,我们要实现一个具有以下功能的任务列表应用:
- 创建新任务
- 列出所有任务
- 根据 ID 获取任务
- 完成任务并更新其状态
我们需要将每个功能作为一个单独的操作来实现。 这就是CQRS的核心理念。
看看第一个操作:创建新任务。 我们需要编写一个命令处理程序,该处理程序将处理输入数据并包装成应该添加到事件存储库中的事件。 在cqrs-example-php中,命令在 `CQRS\Command` 接口中定义,我们需要实现我们自己的命令类。
```php
interface Command {
public function getName(): string;
public function getData(): array;
}
class CreateTaskCommand implements Command {
public function __construct(string $name) {
// ...
}
public function getName(): string {
return 'createTask';
}
public function getData(): array {
return [
'name' =>$this->name
];
}
}
```
现在我们需要编写命令处理程序,将创建新任务并将其添加到事件存储库中。 在cqrs-example-php中,命令处理程序有一个映射表,该映射表将命令名称映射到实际的处理程序。
```php
$commandBus = new CQRS\CommandBus\CommandBus();
$commandBus->register('createTask', new CreateTaskHandler($eventStore));
```
注意,在这里我忽略了依赖项注入,为了避免混淆,将 `EventStore` 注入命令处理程序的构造函数中。
最后,我们需要编写一个查询处理程序,以便用户可以查看任务列表并更新其状态。 因为这个应用程序非常简单,我们将把所有的读取和写入操作放在一个处理程序中。 在cqrs-example-php中,这个概念被称为“查询服务”。
```php
class TaskQueryHandler implements CQRS\QueryHandler {
public function handle($query) {
// ...
}
}
```
有了处理程序和查询服务,我们现在可以使用这些部件来构建我们的整个应用程序了。
```php
$eventStore = ... // initialize event store
$commandBus = new CQRS\CommandBus\CommandBus();
$commandBus->register('createTask', new CreateTaskHandler($eventStore));
$commandBus->register('completeTask', new UpdateTaskHandler($eventStore));
$taskService = new TaskQueryHandler($eventStore);
// create a new task
$commandBus->dispatch(new CreateTaskCommand('Buy milk'));
// list all tasks
$tasks = $taskService->handle(new AllTasksQuery());
// update task status
$commandBus->dispatch(new UpdateTaskCommand($taskId, TaskStatus::COMPLETED));
```
cqrs-example-php是一个相当完整的示例,涵盖了许多与CQRS架构相关的概念。 它也很容易修改和扩展。 可以通过查看 README 文件和示例实现代码来更好地理解其中的细节。