itantik/cq-dispatcher

支持中间件的命令查询调度器。

v0.5.1 2020-06-08 15:23 UTC

This package is auto-updated.

Last update: 2024-09-09 01:09:36 UTC


README

支持中间件的命令查询调度器。该库可以应用于根据 命令查询分离 (CQS) 或 命令查询责任分离 (CQRS) 原则设计的应用程序。

CQ Dispatcher 通常位于 应用服务层,其客户端为来自 UI 层 的控制器和展示者。它调度应用请求,这些请求是命令和查询,找到合适的处理器并执行它。

命令 表示一个通过命令处理器执行操作的请求。它 修改 数据或改变对象的状态。它 不返回 值。

查询 表示一个通过查询处理器获取数据的请求。它 不得修改 数据。它 返回 一个值。

安装

composer require itantik/cq-dispatcher

用法

CQ Dispatcher 需要一个依赖注入容器。您必须定义一个适配器到您的 DI 容器。适配器实现 Itantik\CQDispatcher\DI\IContainer

与 Nette 框架一起使用

安装 itantik/nette-cq-dispatcher 扩展,用于 Nette 框架。它已配置为使用 Nette DI 容器。

文件结构示例

假设应用服务层具有以下文件结构

- UserService
    - UserCommands.php  // command dispatcher
    - UserQueries.php   // query dispatcher
    - Command
        - AddUserCommand.php
        - AddUserHandler.php
        - ChangePasswordCommand.php
        - ChangePasswordHandler.php
        - ... // other commands/handlers
    - Query
        - FindAllUsersQuery.php
        - FindAllUsersHandler.php
        - GetUserQuery.php
        - GetUserHandler.php
        - ... // other queries/handlers
- Middleware
    - TransactionalMiddleware.php

命令

命令是一个实现了 Itantik\Middleware\IRequest 接口的普通对象,其类名使用可选的 Command 后缀。命令对象代表您的请求。

class AddUserCommand implements IRequest
{
    /** @var int */
    private $id;
    /** @var string */
    private $name;
    /** @var string */
    private $surname;

    public function __construct(int $id, string $name, string $surname)
    {
        $this->id = $id;
        $this->name = $name;
        $this->surname = $surname;
    }

    public function id(): int
    {
        return $this->id;
    }

    public function name(): string
    {
        return $this->name;
    }

    public function surname(): string
    {
        return $this->surname;
    }
}

命令处理器

命令处理器实现了 Itantik\CQDispatcher\Command\ICommandHandler 接口,类名应使用 Handler 后缀。命令处理器代表一个应用服务。

class AddUserHandler implements ICommandHandler
{
    /** @var UserRepository */
    private $repository;

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    public function handle(AddUserCommand $command): void
    {
        $user = new User($command->id(), $command->name(), $command->surname());
        $this->repository->add($user);
    }
}

命令调度器

命令调度器根据命令类名创建适当的命令处理器,然后调用其 handle 方法。对于每个命令,都有一个单独的命令处理器。

默认情况下,命令与其处理器的配对基于类名。

命令 Namespace\SomeCommand 使用 Namespace\SomeHandler 处理器。 Command 后缀是可选的,因此您可以命名命令为 Namespace\Some 而不是 Namespace\SomeCommand

创建命令调度器

命令调度器扩展了抽象类 \Itantik\CQDispatcher\Commands

class UserCommands extends \Itantik\CQDispatcher\Commands
{
}

在控制器和展示者中使用

// simplified code

class UserController
{
    /** @var UserCommands */
    private $userCommands;

    public function actionAddUser(string $name, string $surname): void
    {
        // create a command
        $id = SomeIdGenerator::next();
        $command = new AddUserCommand($id, $name, $surname);
        try {
            // execute command
           $this->userCommands->execute($command);
        } catch (\Exception $ex) {
            // handle error
            // ...
        }
        // redirect to user detail page
        // ...
    }
}

扩展中间件

命令调度器内置了对 itantik/middleware 的支持。

例如,将数据库事务包装在每个命令处理器周围的中间件可能看起来像这样

final class TransactionalMiddleware implements Itantik\Middleware\IMiddleware
{
    /** @var DatabaseConnection */
    private $connection;


    public function __construct(DatabaseConnection $connection)
    {
        $this->connection = $connection;
    }

    public function handle(IRequest $request, ILayer $nextLayer): IResponse
    {
        $connection = $this->connection;
        $connection->beginTransaction();
        try {
            $res = $nextLayer->handle($request);
            $connection->commit();
            return $res;
        } catch (Exception $e) {
            $connection->rollback();
            throw $e;
        }
    }
}

扩展命令调度器

class UserCommands extends \Itantik\CQDispatcher\Commands
{
    public function __construct(
        ICommandDispatcher $commandDispatcher,
        DatabaseConnection $connection
    ) {
        parent::__construct($commandDispatcher);
        $this->appendMiddleware(new TransactionalMiddleware($connection));
    }
}

在控制器中使用没有改变。现在每个命令都在数据库事务中执行。

查询

查询实现了 Itantik\Middleware\IRequest 接口,其类名使用可选的 Query 后缀。

class FindAllUsersQuery implements IRequest
{
}

查询处理器

查询处理器实现了 Itantik\CQDispatcher\Query\IQueryHandler 接口,类名应使用 Handler 后缀,handle 方法返回一个值。

class FindAllUsersHandler implements IQueryHandler
{
    /** @var UserRepository */
    private $repository;

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    public function handle(FindAllUsersQuery $query): UserList
    {
        return $this->repository->findAll();
    }
}

查询调度器

类似于命令调度器。

查询 Namespace\SomeQuery 使用 Namespace\SomeHandler 处理器。 Query 后缀是可选的,因此您可以命名查询为 Namespace\Some 而不是 Namespace\SomeQuery

创建查询调度器

查询调度器扩展了抽象类 \Itantik\CQDispatcher\Queries

class UserQueries extends \Itantik\CQDispatcher\Queries
{
}

在控制器和展示者中使用

// simplified code

class UserController
{
    /** @var UserQueries */
    private $userQueries;

    public function actionAllUsers(): void
    {
        // create a query
        $query = new FindAllUsersQuery();
        // execute query
       $userList = $this->userQueries->execute($query);

        // fill-in template
        // ...
    }
}

扩展中间件

与命令调度器相同。

需求

  • PHP 7.2