icanboogie / message-bus
消息调度器的简单实现
Requires
- php: >=8.0.2
Requires (Dev)
- olvlvl/composer-attribute-collector: ^1.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^9.5
- psr/container: ^1.0|^2.0
- symfony/config: ^6.0
- symfony/dependency-injection: ^6.0
- symfony/yaml: ^6.0
README
消息调度器通过将各种来源的输入映射到更简单的应用程序消息来帮助分离展示关注点和业务逻辑。它还帮助将领域与实现解耦,因为应用程序只需要了解消息,而不需要了解它们是如何处理的。这是在六边形架构中广为人知的设计。
ICanBoogie/MessageBus 提供了一个消息调度器的实现,支持权限和投票者。还有一个简单的消息处理程序提供者实现,还有一个更复杂的实现,它与 PSR-11 容器一起工作。最后,还特别支持 Symfony 的依赖注入组件。
使用消息调度器可以像以下示例一样简单
<?php namespace ICanBoogie\MessageBus; /* @var Dispatcher $dispatcher */ /* @var object $message */ // The message is dispatched to its handler, the result is returned. $result = $dispatcher->dispatch($message);
安装
composer require icanboogie/message-bus
如果您正在升级到较新版本,请检查迁移指南。
消息
消息可以是任何类型的对象。重要的是,所有输入类型考虑(例如 HTTP 详细信息)都应删除以保留对领域的本质。也就是说,控制器会创建一个要分发的消息,但控制器关注点将保留在控制器中,它们不会泄漏到消息中。
以下示例演示了如何定义一个 DeleteMenu
消息。请注意,没有输入类型或授权的概念。这些都是展示关注点,它们应该保留在那里。
<?php // … final class DeleteMenu { public function __construct( public /*readonly*/ int $id ) { } }
不更改应用程序状态的消息(换句话说,导致只读操作的消息)可以用 Safe
接口标记。这不是要求,而是一些建议,以帮助您识别消息。
<?php use ICanBoogie\MessageBus\Safe; // … final class ShowMenu implements Safe { public function __construct( public /*readonly*/ int $id ) { } }
消息处理程序
消息处理程序处理消息。通常关系是 1:1,即一个处理程序对应一个消息类型。消息处理程序是可调用的,通常是实现 __invoke(T $message)
的类,其中 T
是消息类型。
以下示例演示了如何为 ShowMenu
消息定义处理程序
<?php final class ShowMenuHandler { public function __invoke(ShowMenu $message): Menu { // … } }
提供消息处理程序
消息处理程序通过提供者获得,通常由服务容器支持。
以下示例演示了如何为给定消息获取消息处理程序,以及如何调用该处理程序以获取结果。
<?php /* @var ICanBoogie\MessageBus\HandlerProvider $provider */ /* @var object $message */ $handler = $provider->getHandlerForMessage($message); $result = $handler($message);
使用 PSR 容器提供处理程序
处理程序可以通过 PSR\HandlerProviderWithContainer 的实例提供,它由 PSR 容器 支持。您需要提供“消息类”到“处理程序服务标识符”的映射。
<?php use ICanBoogie\MessageBus\PSR\HandlerProviderWithContainer; use Psr\Container\ContainerInterface; /* @var $container ContainerInterface */ $handlerProvider = new HandlerProviderWithContainer($container, [ Acme\MenuService\Application\MessageBus\CreateMenu::class => Acme\MenuService\Application\MessageBus\CreateMenuHandler::class, Acme\MenuService\Application\MessageBus\DeleteMenu::class => Acme\MenuService\Application\MessageBus\DeleteMenuHandler::class, ]);
使用 Symfony 的依赖注入组件提供处理程序
定义消息、处理程序、权限和投票者的最简单方法是使用 Symfony 的依赖注入 组件。将处理程序定义为服务,使用标签来识别它们及其支持的消息类型。还可以定义权限及其投票者。
您可以直接在项目中使用提供的 services.yaml
文件,并结合使用编译器传递 MessageBusPass。
以下示例演示了如何定义处理器、命令、权限和投票者
services: Acme\MenuService\Application\MessageBus\CreateMenuHandler: tags: - { name: message_bus.handler, message: Acme\MenuService\Application\MessageBus\CreateMenu } - { name: message_bus.permission, permission: is_admin } - { name: message_bus.permission, permission: can_write_menu } Acme\MenuService\Application\MessageBus\DeleteMenuHandler: tags: - { name: message_bus.handler, message: Acme\MenuService\Application\MessageBus\DeleteMenu } - { name: message_bus.permission, permission: is_admin } - { name: message_bus.permission, permission: can_manage_menu } Acme\MenuService\Presentation\Security\Voters\IsAdmin: tags: - { name: message_bus.voter, permission: is_admin } Acme\MenuService\Presentation\Security\Voters\CanWriteMenu: tags: - { name: message_bus.voter, permission: can_write_menu } Acme\MenuService\Presentation\Security\Voters\CanManageMenu: tags: - { name: message_bus.voter, permission: can_manage_menu }
<?php use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\Config\FileLocator; use ICanBoogie\MessageBus\Symfony\MessageBusPass; /* @var string $config */ $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); $loader->load($config); $container->addCompilerPass(new MessageBusPass()); $container->compile();
使用 PHP 8 属性代替 YAML
使用 composer-attribute-collector Composer 插件,可以使用 PHP 8 属性代替 YAML 来定义处理器、权限和投票者。
对于处理器和权限,可以使用 Handler 和 Permission 属性来替换 YAML
namespace Acme\MenuService\Application\MessageBus; use ICanBoogie\MessageBus\Attribute\Handler; use ICanBoogie\MessageBus\Attribute\Permission; #[Permission('is_admin')] #[Permission('can_write_menu')] final class CreateMenu { // ... } #[Handler] final class CreateMenuHandler { // ... }
对于投票者,可以使用 Vote 属性来替换 YAML
<?php namespace Acme\MenuService\Presentation\Security\Voters; use ICanBoogie\MessageBus\Attribute\Vote; #[Vote('can_write_menu')] final class CanWriteMenu { // ... }
Acme\MenuService\Application\MessageBus\CreateMenuHandler: tags: - { name: message_bus.handler, message: Acme\MenuService\Application\MessageBus\CreateMenu } - { name: message_bus.permission, permission: is_admin } - { name: message_bus.permission, permission: can_write_menu }
只需在 MessageBusPass 之前添加编译器传递 MessagePubPassWithAttributes
<?php // ... $container->addCompilerPass(new MessageBusPassWithAttributes()); $container->addCompilerPass(new MessageBusPass()); // ...
使用提供者链提供处理器
使用 HandlerProviderWithChain
,您可以连接多个处理器提供者。它们将按顺序使用,直到找到处理器。
<?php namespace ICanBoogie\MessageBus; /* @var HandlerProviderWithHandlers $providerWithHandlers */ /* @var PSR\HandlerProviderWithContainer $providerWithContainer */ $provider = new HandlerProviderWithChain([ $providerWithHandlers, $providerWithContainer ]); /* @var object $message */ $handler = $provider->getHandlerForMessage($message);
权限和投票者
您可能希望根据某些条件限制消息的派发。例如,删除记录应仅适用于具有特定作用域的用户的 JWT。为此,您需要确保以下几点
-
定义投票者和它们投票的权限。
services: Acme\MenuService\Presentation\Security\Voters\CanManageMenu: tags: - { name: message_bus.voter, permission: can_manage_menu }
-
将权限与处理器和消息定义一起标记。
services: Acme\MenuService\Application\MessageBus\DeleteMenuHandler: tags: - { name: message_bus.handler, message: Acme\MenuService\Application\MessageBus\DeleteMenu } - { name: message_bus.permission, permission: can_manage_menu }
-
使用 RestrictedDispatcher 而不是 Dispatcher。
<?php // ... use ICanBoogie\MessageBus\RestrictedDispatcher; final class MenuController { public function __construct( private RestrictedDispatcher $dispatcher ) {} }
-
在上下文中放入投票者做出决定所需的任何内容。在以下示例中,这将是一个令牌,供投票者检查作用域。
<?php // ... use ICanBoogie\MessageBus\Context; final class MenuController { // ... public function delete(Request $request): Response { // ... $this->dispatch( new MenuDelete($id), new Context([ $token ]) ); } }
持续集成
项目由 GitHub actions 持续测试。
行为准则
本项目遵守 贡献者行为准则。通过参与本项目及其社区,您应遵守此准则。
贡献
请参阅 CONTRIBUTING 了解详细信息。
许可证
icanboogie/message-bus 在 BSD-3-Clause 许可下发布。