nepada/message-bus

基于 symfony/messenger 构建的有观点的消息总线。

v3.0.0 2024-04-01 04:15 UTC

This package is auto-updated.

Last update: 2024-09-01 04:14:13 UTC


README

Build Status Coverage Status Downloads this Month Latest stable

基于 symfony/messenger 构建,主要基于 damejidlo/message-bus 的想法和代码库,最初由 Ondřej Bouda 开发。

安装

通过 Composer

$ composer require nepada/message-bus

约定

我们定义了两种类型的消息和相应的消息总线 - 命令和事件。

命令

命令实现必须遵守以下规则

  • 类必须实现 Nepada\Commands\Command 接口
  • 类必须命名为 <command-name>Command
  • 类必须是最终的
  • 类必须是只读的
  • 命令名称应该使用祈使句形式("做某事")
  • 命令必须是一个简单的不可变 DTO
  • 命令不得包含实体,只包含引用(例如 int $orderId,而不是 Order $order

良好的命令类名称示例

  • RejectOrderCommand
  • CreateUserCommand

命令处理器实现必须遵守以下规则

  • 类必须实现 Nepada\Commands\CommandHandler 接口
  • 类必须命名为 <command-name>Handler
  • 类必须是最终的
  • 类必须实现名为 __invoke 的方法
  • __invoke 方法必须有一个名为 $command 的参数
  • __invoke 方法参数必须使用特定命令类的类型提示
  • __invoke 方法的返回类型必须是 void
  • 如果可以抛出特定异常,则 __invoke 方法必须使用 @throws 标签注解

示例

final class DoSomethingHandler implements \Nepada\MessageBus\Commands\CommandHandler
{
    /**
     * @throws SomeException
     */
    public function __invoke(DoSomethingCommand $command): void
    {
        // ...
    }
}

每个命令必须恰好有一个处理器。

事件

事件仅在命令处理期间分发。

事件实现必须遵守以下规则

  • 类必须实现 Nepada\Events\Event 接口
  • 类必须命名为 <event-name>Event
  • 类必须是最终的
  • 类必须是只读的
  • 事件名称应该使用过去时("发生了某事")
  • 事件必须是一个简单的不可变 DTO
  • 事件不得包含实体,只包含引用(例如 int $orderId,而不是 Order $order

良好的事件类名称示例

  • OrderRejectedEvent
  • UserRegisteredEvent

事件订阅器实现必须遵守以下规则

  • 类必须实现 Nepada\Events\EventSubscriber 接口
  • 类必须命名为 <do-something>On<event-name>
  • 类必须是最终的
  • 类必须实现名为 __invoke 的方法
  • __invoke 方法必须有一个名为 $event 的参数
  • __invoke 方法参数必须使用特定事件类的类型提示
  • __invoke 方法的返回类型必须是 void
  • 如果可以抛出特定异常,则 __invoke 方法必须使用 @throws 标签注解

示例

final class DoSomethingOnSomethingHappened implements \Nepada\MessageBus\Events\EventSubscriber
{
     public function __invoke(SomethingHappenedEvent $event): void {}
}

每个事件可以有任何数量的订阅者,或者根本没有订阅者。

配置 & 使用

通过静态分析强制执行约定

上述大多数约定可以通过静态分析强制执行。分析应在编译 DI 容器时运行,不建议在应用程序运行时触发。

use Nepada\MessageBus\StaticAnalysis\ConfigurableHandlerValidator;
use Nepada\MessageBus\StaticAnalysis\HandlerType;
use Nepada\MessageBus\StaticAnalysis\MessageHandlerValidationConfiguration;

// Validate command handler
$commandHandlerType = HandlerType::fromString(DoSomethingHandler::class);
$commandHandlerConfiguration = MessageHandlerValidationConfiguration::command();
$commandHandlerValidator = new ConfigurableHandlerValidator($commandHandlerConfiguration);
$commandHandlerValidator->validate($commandHandlerType);

// Validate event subscriber
$eventSubscriberType = HandlerType::fromString(DoSomethingOnSomethingHappened::class);
$eventSubscriberConfiguration = MessageHandlerValidationConfiguration::event();
$eventSubscriberValidator = new ConfigurableHandlerValidator($eventSubscriberConfiguration);
$eventSubscriberValidator->validate($eventSubscriberType);

尖端技术(新规则)

为了保持向后兼容性,默认情况下不强制执行新规则。它们可以在创建验证器配置时通过传递 $bleedingEdge 标志来启用,例如 MessageHandlerValidationConfiguration::command(true)

这些规则将在下一个主要版本中启用:(目前没有)

从处理器类中提取处理的消息类型

使用 MessageTypeExtractor 检索特定命令处理器或事件订阅器处理的消息类型

use Nepada\MessageBus\StaticAnalysis\HandlerType;
use Nepada\MessageBus\StaticAnalysis\MessageTypeExtractor;

// Extracting handled message type
$messageTypeExtractor = new MessageTypeExtractor();
$commandHandlerType = HandlerType::fromString(DoSomethingHandler::class);
$messageTypeExtractor->extract($commandHandlerType); // MessageType instance for DoSomethingCommand

日志记录

LoggingMiddleware 实现将日志记录到标准 PSR-3 日志记录器中。消息处理开始及其成功或失败情况分别记录。日志上下文填充了从命令或事件 DTO 提取的属性。

嵌套消息处理

通常,不建议在另一个命令处理程序内部执行命令。您可以使用 PreventNestedHandlingMiddleware 完全禁止这种行为。

配置

您可以根据自己的需要使用提供的构建块与 Symfony Messenger 一起配置一个或多个命令和/或事件总线实例。

一个纯 PHP 的最小设置可能如下所示

use Nepada\MessageBus\Commands\CommandHandlerLocator;
use Nepada\MessageBus\Commands\MessengerCommandBus;
use Nepada\MessageBus\Events\EventSubscribersLocator;
use Nepada\MessageBus\Events\MessengerEventDispatcher;
use Nepada\MessageBus\Logging\LogMessageResolver;
use Nepada\MessageBus\Logging\MessageContextResolver;
use Nepada\MessageBus\Logging\PrivateClassPropertiesExtractor;
use Nepada\MessageBus\Middleware\LoggingMiddleware;
use Nepada\MessageBus\Middleware\PreventNestedHandlingMiddleware;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;

$dispatchAfterCurrentBusMiddleware = new DispatchAfterCurrentBusMiddleware();
$preventNestedHandlingMiddleware = new PreventNestedHandlingMiddleware();
$loggingMiddleware = new LoggingMiddleware(
    new LogMessageResolver(),
    new MessageContextResolver(
        new PrivateClassPropertiesExtractor(),
    ),
    $psrLogger,
);
$handleCommandMiddleware = new HandleMessageMiddleware(
    new CommandHandlerLocator(
        $psrContainer,
        [
            DoSomethingCommand::class => 'doSomethingHandlerServiceName',
        ],
    ),
);
$handleEventMiddleware = new HandleMessageMiddleware(
    new EventSubscribersLocator(
        $psrContainer,
        [
            SomethingHappenedEvent::class => [
                'doSomethingOnSomethingHappenedServiceName',
                'doSomethingElseOnSomethingHappenedServiceName',
            ],
        ],
    ),
);
$eventDispatcher = new MessengerEventDispatcher(
    new MessageBus([
        $dispatchAfterCurrentBusMiddleware,
        $loggingMiddleware,
        $handleEventMiddleware,
    ]),
);
$commandBus = new MessengerCommandBus(
    new MessageBus([
        $dispatchAfterCurrentBusMiddleware,
        $loggingMiddleware,
        $preventNestedHandlingMiddleware,
        $handleCommandMiddleware,
    ]),
);

注意 DispatchAfterCurrentBusMiddleware 的使用 - 这是必要的,以确保在命令处理过程中产生的事件仅在命令处理 成功 完成后才进行处理。

对于 Nette 框架集成,请考虑使用 nepada/message-bus-nette

扩展

致谢

代码库的静态分析部分以及许多其他核心想法借鉴自 damejidlo/message-bus,最初由 Ondřej Bouda 开发。