nepada/message-bus 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

nepada/message-bus

Composer 安装命令:

composer require nepada/message-bus

包简介

Opinionated message bus built on top of symfony/messenger.

README 文档

README

Build Status Coverage Status Downloads this Month Latest stable

Opinionated message bus built on top of symfony/messenger largely based on the ideas and code base of damejidlo/message-bus, originally developed by Ondřej Bouda.

Installation

Via Composer:

$ composer require nepada/message-bus

Conventions

We define two types of messages and corresponding message buses - commands and events.

Commands

Command implementation must adhere to these rules:

  • class must implement Nepada\Commands\Command interface
  • class must be named <command-name>Command
  • class must be final
  • class must be readonly
  • command name should be in imperative form ("do something")
  • command must be a simple immutable DTO
  • command must not contain entities, only references (i.e. int $orderId, not Order $order)

Examples of good command class names:

  • RejectOrderCommand
  • CreateUserCommand

Command handler implementation must adhere to these rules:

  • class must implement Nepada\Commands\CommandHandler interface
  • class must be named <command-name>Handler
  • class must be final
  • class must implement method named __invoke
  • __invoke method must have exactly one parameter named $command
  • __invoke method parameter must be typehinted with specific command class
  • __invoke method return type must be void
  • __invoke method must be annotated with @throws tags if specific exceptions can be thrown

Example:

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

Every command must have exactly one handler.

Events

Events must be dispatched during command handling only.

Event implementation must adhere to these rules:

  • class must implement Nepada\Events\Event interface
  • class must be named <event-name>Event
  • class must be final
  • class must be readonly
  • event name should be in past tense ("something happened")
  • event must be a simple immutable DTO
  • event must not contain entities, only references (i.e. int $orderId, not Order $order)

Examples of good event class names:

  • OrderRejectedEvent
  • UserRegisteredEvent

Event subscriber implementation must adhere to these rules:

  • class must implement Nepada\Events\EventSubscriber interface
  • class must be named <do-something>On<event-name>
  • class must be final
  • class must implement method named __invoke
  • __invoke method must have exactly one parameter named $event
  • __invoke method parameter must be typehinted with specific event class
  • __invoke method return type must be void
  • __invoke method must be annotated with @throws tags if specific exceptions can be thrown

Example:

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

Every event may have any number of subscribers, or none at all.

Configuration & Usage

Enforcing conventions by static analysis

Most of the conventions described above may be enforced by static analysis. The analysis should be run during the compilation of DI container, triggering it at application runtime is not recommended.

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);

Bleeding edge (new rules)

To maintain backwards compatibility new rules are not enforced by default. They can be enabled by passing $bleedingEdge flag when creating validator configuration, e.g. MessageHandlerValidationConfiguration::command(true).

These rules will be enabled in the next major version: (none at the moment)

Extracting handled message type from handler class

Use MessageTypeExtractor to retrieve the message type that a given command handler or event subscriber handles:

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

Logging

LoggingMiddleware implements logging into standard PSR-3 logger. Start of message handling and its success or failure are logged separately. Logging context is filled with the extracted attributes of command or event DTO.

Nested message handling

Generally, it's not a good idea to execute commands from within another command handler. You can completely forbid this behavior with PreventNestedHandlingMiddleware.

Configuration

It is completely up to you to use the provided building blocks together with Symfony Messenger and configure one or more instances of command and/or event buses.

A minimal setup in pure PHP might look something like this:

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,
    ]),
);

Note the usage of DispatchAfterCurrentBusMiddleware - this is necessary to ensure that events produced during the handling of a command are handled only after the command handling successfully finishes.

For Nette Framework integration, consider using nepada/message-bus-nette.

Extensions

Credits

Static analysis part of the code base and a lot of other core ideas are borrowed from damejidlo/message-bus, originally developed by Ondřej Bouda.

nepada/message-bus 适用场景与选型建议

nepada/message-bus 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 33.16k 次下载、GitHub Stars 达 4, 最近一次更新时间为 2020 年 07 月 21 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「Messenger」 「message bus」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 nepada/message-bus 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 nepada/message-bus 我们能提供哪些服务?
定制开发 / 二次开发

基于 nepada/message-bus 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 33.16k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 4
  • 点击次数: 20
  • 依赖项目数: 3
  • 推荐数: 0

GitHub 信息

  • Stars: 4
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: BSD-3-Clause
  • 更新时间: 2020-07-21