В просторечии больше известен как шаблон command bus , но библиотека проводит различие между командами и запросами и позволяет вам принудительно не возвращать значения в обработчиках команд, чтобы соответствовать шаблону CQRS.
Это автономная библиотека , единственными двумя зависимостями которой являются интерфейсы контейнера PSR-11 и журнала PSR-3, обеспечивающие лучшую совместимость.
Оглавление:
Установите библиотеку с помощью композитора:
composer require sco/message-bus
Вам нужно будет следовать стандарту автозагрузки PSR-4 и либо создать свой собственный класс сервисного контейнера, который является вопросом реализации PsrContainerContainerInterface
и может быть таким же простым, как тот, который библиотека использует для своего набора тестов ScoMessageBusTestsStubContainerInMemoryContainer
, или вы можете композитору потребовать библиотеку сервисного контейнера, которая соответствует стандарту PSR-11, например PHP-DI.
require ' vendor/autoload.php '
$ container = new InMemoryContainer( $ services )
$ bus = new Sco MessageBus Bus ( $ container );
$ bus -> dispatch ( new FindPostByIdQuery ( 1 ))
Здесь мы можем использовать два подхода: украсить класс Bus, предоставляемый библиотекой, или внедрить локатор служб. Для получения дополнительной информации вы можете прочитать Документы Symfony.
Мы можем создать новый класс Decorator, который будет реализовывать интерфейс Symfony SymfonyContractsServiceServiceSubscriberInterface
:
use Sco MessageBus Bus ;
use Sco MessageBus Message ;
use Sco MessageBus Result ;
use Psr Container ContainerInterface ;
use Symfony Contracts Service ServiceSubscriberInterface ;
class MessageBus implements ServiceSubscriberInterface
{
private Bus $ bus ;
public function __construct ( ContainerInterface $ locator )
{
$ this -> bus = new Bus ( $ locator , [], null , new UuidV4Identity ());
}
public function dispatch ( Sco MessageBus Message $ message ): Result
{
return $ this -> bus -> dispatch ( $ message );
}
public static function getSubscribedServices (): array
{
return [
FindPostByIdHandler::class,
SavePostHandler::class
];
}
}
При таком подходе все обработчики в вашем приложении должны быть добавлены в массив, возвращаемый getSubscribedServices
, поскольку службы в Symfony по умолчанию не являются общедоступными, и на самом деле они не должны быть общедоступными, поэтому, если вы не добавите свои обработчики в этот массив, когда картограф завершено сопоставление, он не сможет найти обработчик, и будет выброшено исключение контейнера службы, не найденной.
Другой подход — внедрить локатор службы со всеми обработчиками в шину библиотеки. Это будет сделано в файлах yaml регистрации службы.
Анонимный поиск сервисов:
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two '
Явное определение локатора службы:
services :
_defaults :
autowire : true
autoconfigure : true
# Explicit Service Locator
message_handler_service_locator :
class : SymfonyComponentDependencyInjectionServiceLocator
arguments :
- ' @FindPostByIdHandler '
- ' @SavePostHandler '
ScoMessageBusBus :
arguments :
$container : ' @message_handler_service_locator '
Давайте расширим эти конфигурации и воспользуемся функцией тегов сервисного контейнера Symfony для автоматического добавления обработчиков в шину:
Использование !tagged_locator
:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handler
Явное определение локатора службы:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Explicit Service Locator
message_handler_service_locator :
class : SymfonyComponentDependencyInjectionServiceLocator
arguments :
- !tagged_iterator message_handler
ScoMessageBusBus :
arguments :
$container : ' @message_handler_service_locator '
Чтобы эффективно использовать его с платформой Laravel, все, что вам нужно сделать, это зарегистрировать Bus в сервисном контейнере Laravel и предоставить контейнер в качестве аргумента библиотечному классу Bus:
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
});
Каждой команде или запросу и соответствующей комбинации объектов Result будет присвоен уникальный идентификатор, например команда, а соответствующий объект Result будет иметь идентификатор 00000001
. Это может быть полезно для целей ведения журнала, аудита или отладки.
Стратегия создания удостоверений по умолчанию — это простой генератор ScoMessageBusIdentityRandomString
позволяющий свести внешние зависимости к минимуму. Чтобы использовать что-то еще, вам может потребоваться библиотека типа https://github.com/ramsey/uuid и реализовать ScoMessageBusIdentity
.
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}
FindPostByIdQuery
будет сопоставлен с FindPostByIdHandler
, а SavePostCommand
будет сопоставлен с SavePostHandler
.#[IsCommand(handler: SavePostHandler::class)]
или #[IsQuery(handler: FindPostByIdHandler::class)]
в ваш класс Command/Query. Имя параметра handler
можно опустить, это зависит от ваших личных предпочтений.ScoMessageBusMapper
.Каждая команда будет передаваться через цепочку промежуточных программ. По умолчанию цепочка пуста, но библиотека предлагает некоторое промежуточное ПО «из коробки»:
begin
, commit
и rollback
представляют собой простые объекты Closure
, поэтому вы можете использовать любой подход ORM или Persistence, который вы предпочитаете. Чтобы создать собственное промежуточное программное обеспечение, вам необходимо реализовать интерфейс ScoMessageBusMiddleware
и предоставить его шине:
use Sco MessageBus Bus ;
use Sco MessageBus Message ;
use Sco MessageBus Middleware ;
class CustomMiddleware implements Middleware
{
public function __invoke ( Message $ message , Closure $ next ) : mixed
{
// Do something before message handling
$ result = $ next ( $ message );
// Do something after message handling
return $ result ;
}
}
$ bus = new Bus (middlewares: [ new CustomMiddleware ()]);
Если вы добавите ScoMessageBusMiddlewareEventMiddleware
вы сможете подписаться на следующие события:
MessageReceivedEvent — возникает при получении сообщения, но до его обработки.
use Sco MessageBus Event Subscriber ;
use Sco MessageBus Event MessageReceivedEvent ;
$ subscriber = new Subscriber ();
$ subscriber -> addListener (MessageReceivedEvent::class, function ( MessageReceivedEvent $ event ) {
$ event -> getName (); // Name of the Event
$ event -> getMessage ();; // Command or Query that has been received
});
MessageHandledEvent — возникает после успешной обработки сообщения.
use Sco MessageBus Event Subscriber ;
use Sco MessageBus Event MessageHandledEvent ;
$ subscriber = new Subscriber ();
$ subscriber -> addListener (MessageHandledEvent::class, function ( MessageHandledEvent $ event ) {
$ event -> getName (); // Name of the Event
$ event -> getMessage (); // Command or Query being handled
$ event -> getResult (); // Result for the handled message
});
MessageFailedEvent — возникает, когда обработка сообщения завершается сбоем и возникает исключение.
use Sco MessageBus Event Subscriber ;
use Sco MessageBus Event MessageFailedEvent ;
$ subscriber = new Subscriber ();
$ subscriber -> addListener (MessageFailedEvent::class, function ( MessageFailedEvent $ event ) {
$ event -> getName (); // Name of the Event
$ event -> getMessage (); // Command or Query being handled
$ event -> getError (); // Captured Exception
});
Промежуточное программное обеспечение транзакций принимает три аргумента функции, каждый для каждого этапа транзакции: начало, фиксация и откат. Использование этого подхода позволяет вам использовать любой ORM, который вы предпочитаете, или даже использовать собственный объект PDO для взаимодействия с вашим уровнем персистентности.
$ pdo = new PDO ( ' {connection_dsn} ' )
$ transaction = new Sco MessageBus Middleware TransactionMiddleware (
fn (): bool => $ pdo -> beginTransaction (),
fn (): bool => $ pdo -> commit (),
fn ( Throwable $ error ): bool => $ pdo -> rollBack (),
);
Библиотека объединяет возвращаемые значения обработчика в объекты значений результатов , чтобы обеспечить согласованный API и чтобы вы могли быть уверены в том, что возвращаемые значения всегда будут одного и того же типа.
Все объекты значений Result расширяют абстрактный класс ScoMessageBusResult
и могут быть разделены на 3 группы:
ScoMessageBusResultBoolean
ScoMessageBusResultInteger
ScoMessageBusResultNumeric
ScoMessageBusResultText
ScoMessageBusResultNone
(обертывает нулевые значения)ScoMessageBusResultDelegated
, который оборачивает объекты и делегирует вызовы свойств и методов базовому объекту.ScoMessageBusResultCollection
и ScoMessageBusResultMap
которые обертывают числовые индексированные массивы (списки) и строковые индексированные массивы (карты) и реализуют интерфейсы Countable
, ArrayAccess
и IteratorAggregate
Вы также можете добавить свои собственные объекты значений Result, расширив абстрактный класс ScoMessageBusResult
и вернув их в соответствующем обработчике.
Библиотека соответствует стандарту PSR-12.