nepada / message-bus
基于 symfony/messenger 构建的有观点的消息总线。
Requires
- php: >=8.2.0 <8.4
- psr/container: ^1.0 || ^2.0
- psr/log: ^1.1 || ^2.0 || ^3.0
- symfony/messenger: ^6.4@dev || ^7.0@dev
Requires (Dev)
- composer-runtime-api: ^2.0
- composer/semver: 3.4.0
- nepada/coding-standard: 7.14.0
- nepada/phpstan-nette-tester: 1.1.0
- nette/tester: 2.5.2
- php-parallel-lint/php-parallel-lint: 1.4.0
- phpstan/phpstan: 1.10.66
- phpstan/phpstan-strict-rules: 1.5.2
- shipmonk/phpstan-rules: 2.11.3
- spaze/phpstan-disallowed-calls: 3.1.2
Suggests
- nepada/message-bus-doctrine: for Doctrine ORM integration
This package is auto-updated.
Last update: 2024-09-01 04:14:13 UTC
README
基于 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。
扩展
- nepada/message-bus-doctrine Doctrine ORM 集成 - 事务处理,从实体收集和发出领域事件等。
- nepada/message-bus-nette Nette 框架 DI 扩展。
- nepada/phpstan-message-bus 为命令处理程序抛出的已检查异常添加对命令总线调用者的传播支持
致谢
代码库的静态分析部分以及许多其他核心想法借鉴自 damejidlo/message-bus,最初由 Ondřej Bouda 开发。