Coloquialmente mais conhecido como padrão command bus , mas a biblioteca faz uma distinção entre comandos e consultas e permite que você não imponha valores de retorno em manipuladores de comando para mantê-lo alinhado com o padrão CQRS.
Esta é uma biblioteca independente , sendo as únicas duas dependências as interfaces PSR-11 Container e PSR-3 Log para permitir melhor interoperabilidade.
Índice:
Instale a biblioteca usando o compositor:
composer require sco/message-bus
Você precisará seguir o padrão de carregamento automático PSR-4 e criar sua própria classe Service Container, que é uma questão de implementar PsrContainerContainerInterface
e pode ser tão simples quanto o que a biblioteca está usando para seu conjunto de testes ScoMessageBusTestsStubContainerInMemoryContainer
, ou você pode compositor exigir uma biblioteca de contêiner de serviço que siga o padrão PSR-11 como PHP-DI.
require ' vendor/autoload.php '
$ container = new InMemoryContainer( $ services )
$ bus = new Sco MessageBus Bus ( $ container );
$ bus -> dispatch ( new FindPostByIdQuery ( 1 ))
Podemos usar duas abordagens aqui, decorando a classe Bus fornecida pela biblioteca, ou injetando o Service Locator. Para mais informações você pode ler Symfony Docs
Podemos criar uma nova classe Decorator que implementará a interface SymfonyContractsServiceServiceSubscriberInterface
do Symfony:
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
];
}
}
Com esta abordagem, todos os manipuladores em seu aplicativo terão que ser adicionados ao array retornado por getSubscribedServices
, uma vez que os serviços no Symfony não são públicos por padrão, e realmente não deveriam ser, a menos que você adicione seus manipuladores a este array quando o mapeador Quando o mapeamento for concluído, ele não será capaz de encontrar o manipulador e uma exceção de contêiner de serviço não encontrado será lançada.
Uma abordagem diferente seria injetar um Service Locator com todos os manipuladores no Bus da biblioteca. Isso seria feito nos arquivos yaml de registro do serviço.
Localizador de serviço anônimo:
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two '
Definição explícita do localizador de serviço:
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 '
Vamos expandir essas configurações e usar o recurso de tags do contêiner de serviço do Symfony para adicionar automaticamente manipuladores ao barramento:
Usando !tagged_locator
:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handler
Definição explícita do localizador de serviço:
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 '
Para usá-lo efetivamente com o framework Laravel, tudo o que você precisa fazer é registrar o Bus no Service Container do Laravel e fornecer o contêiner como argumento para a classe Bus da biblioteca:
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
});
Cada Comando ou Consulta e seu respectivo combo de objetos Result receberão uma Identidade única, por exemplo, um Comando, e seu respectivo objeto Result terá uma identidade de 00000001
. Isso pode ser útil para fins de registro, auditoria ou depuração.
A estratégia de geração de identidade padrão é um gerador ScoMessageBusIdentityRandomString
simples para manter as dependências externas no mínimo. Para usar outra coisa, você pode precisar de uma biblioteca como https://github.com/ramsey/uuid e implementar o ScoMessageBusIdentity
.
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}
FindPostByIdQuery
será mapeado para FindPostByIdHandler
ou um SavePostCommand
será mapeado para SavePostHandler
.#[IsCommand(handler: SavePostHandler::class)]
ou #[IsQuery(handler: FindPostByIdHandler::class)]
à sua classe Command/Query. O nome do parâmetro handler
pode ser omitido, depende de sua preferência pessoal.ScoMessageBusMapper
.Cada comando será passado por uma cadeia de Middlewares. Por padrão, a cadeia está vazia, mas a biblioteca oferece alguns Middleware prontos para uso:
begin
, commit
e rollback
são objetos Closure
simples, para que você possa usar qualquer abordagem de ORM ou Persistência de sua preferência. Para criar seu próprio middleware customizado você precisa implementar a interface ScoMessageBusMiddleware
e fornecê-la ao barramento:
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 ()]);
Se você adicionar ScoMessageBusMiddlewareEventMiddleware
você poderá assinar os seguintes eventos:
MessageReceivedEvent - gerado quando a mensagem é recebida, mas antes de ser tratada.
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 - gerado após a mensagem ter sido tratada com sucesso.
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 - gerado quando o tratamento da mensagem falha e uma exceção é lançada.
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
});
O Transaction Middleware aceita três argumentos de função, cada um para cada estágio da transação: início, confirmação e reversão. Seguir essa abordagem permite que você use qualquer ORM de sua preferência ou até mesmo use o objeto PDO nativo para interagir com sua camada de persistência.
$ 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 (),
);
A biblioteca agrupa os valores de retorno do manipulador em objetos de valor de resultado para fornecer uma API consistente e para que você possa depender de que os valores de retorno sejam sempre do mesmo tipo.
Todos os objetos de valor Result estendem a classe abstrata ScoMessageBusResult
e podem ser divididos em 3 grupos:
ScoMessageBusResultBoolean
ScoMessageBusResultInteger
ScoMessageBusResultNumeric
ScoMessageBusResultText
ScoMessageBusResultNone
(envolve valores nulos)ScoMessageBusResultDelegated
que agrupa objetos e delega chamadas para propriedades e métodos para o objeto subjacenteScoMessageBusResultCollection
e ScoMessageBusResultMap
que agrupam matrizes indexadas por números (listas) e matrizes indexadas por strings (mapas) e implementam interfaces Countable
, ArrayAccess
e IteratorAggregate
Você também pode adicionar seus próprios objetos de valor Result personalizados estendendo a classe abstrata ScoMessageBusResult
e retornando-os no manipulador apropriado.
A biblioteca segue o padrão PSR-12.