Coloquialmente más conocido como patrón command bus , pero la biblioteca hace una distinción entre comandos y consultas y le permite imponer valores sin retorno en los controladores de comandos para mantenerlo en línea con el patrón CQRS.
Esta es una biblioteca independiente , las dos únicas dependencias son las interfaces PSR-11 Container y PSR-3 Log para permitir una mejor interoperabilidad.
Tabla de contenido:
Instale la biblioteca usando el compositor:
composer require sco/message-bus
Deberá seguir el estándar de carga automática PSR-4 y crear su propia clase de contenedor de servicios, lo cual es cuestión de implementar PsrContainerContainerInterface
y puede ser tan simple como lo que usa la biblioteca para su conjunto de pruebas ScoMessageBusTestsStubContainerInMemoryContainer
, o puede requerir una biblioteca de contenedor de servicios que cumpla con el estándar 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 dos enfoques aquí: decorar la clase Bus proporcionada por la biblioteca o inyectar el Localizador de servicios. Para más información puedes leer Symfony Docs
Podemos crear una nueva clase Decorator que implementará la interfaz SymfonyContractsServiceServiceSubscriberInterface
de 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
];
}
}
Con este enfoque, todos los controladores en su aplicación deberán agregarse a la matriz devuelta por getSubscribedServices
, ya que los servicios en Symfony no son públicos de forma predeterminada, y realmente no deberían serlo, a menos que agregue sus controladores a esta matriz cuando el asignador Una vez finalizado el mapeo, no podrá encontrar el controlador y se generará una excepción de contenedor de servicio no encontrado.
Un enfoque diferente sería inyectar un localizador de servicios con todos los controladores en el bus de la biblioteca. Esto se haría en los archivos yaml de registro del servicio.
Localizador de servicios anónimo:
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two '
Definición explícita del localizador de servicios:
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 '
Ampliemos estas configuraciones y usemos la función de etiquetas del contenedor de servicios de Symfony para agregar automáticamente controladores al Bus:
Usando !tagged_locator
:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handler
Definición explícita del localizador de servicios:
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 usarlo de manera efectiva con el marco de Laravel, todo lo que tiene que hacer es registrar el Bus en el Contenedor de servicios de Laravel y proporcionar el contenedor como argumento para la clase Bus de la biblioteca:
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
});
A cada comando o consulta y su respectivo combo de objetos de resultado se les asignará una identidad única, por ejemplo, un comando, y su respectivo objeto de resultado tendrá una identidad de 00000001
. Esto puede resultar útil para fines de registro, auditoría o depuración.
La estrategia de generación de identidad predeterminada es un generador ScoMessageBusIdentityRandomString
simple para mantener las dependencias externas al mínimo. Para usar algo más, podría necesitar una biblioteca como https://github.com/ramsey/uuid e implementar ScoMessageBusIdentity
.
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}
FindPostByIdQuery
se asignará a FindPostByIdHandler
o un SavePostCommand
se asignará a SavePostHandler
.#[IsCommand(handler: SavePostHandler::class)]
o #[IsQuery(handler: FindPostByIdHandler::class)]
a su clase Comando/Consulta. El nombre del parámetro handler
se puede omitir, depende de sus preferencias personales.ScoMessageBusMapper
.Cada comando pasará a través de una cadena de Middlewares. De forma predeterminada, la cadena está vacía, pero la biblioteca ofrece algo de Middleware listo para usar:
begin
, commit
y rollback
son objetos Closure
simples, por lo que puede usar el enfoque ORM o de persistencia que prefiera. Para crear su propio middleware personalizado, debe implementar la interfaz ScoMessageBusMiddleware
y proporcionarla al bus:
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 ()]);
Si agrega ScoMessageBusMiddlewareEventMiddleware
podrá suscribirse a los siguientes eventos:
MessageReceivedEvent : se genera cuando se recibe el mensaje pero antes de ser manejado.
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 : se genera después de que el mensaje se haya manejado correctamente.
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 : se genera cuando falla el manejo del mensaje y se genera una excepción.
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
});
Transaction Middleware acepta tres argumentos de función, cada uno para cada etapa de la transacción: inicio, confirmación y reversión. Seguir este enfoque le permite usar cualquier ORM que prefiera o incluso usar el objeto PDO nativo para interactuar con su capa de persistencia.
$ 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 (),
);
La biblioteca envuelve los valores de retorno del controlador en objetos de valor de resultado para proporcionar una API consistente y para que pueda depender de que los valores de retorno siempre sean del mismo tipo.
Todos los objetos de valor de resultado amplían la clase abstracta ScoMessageBusResult
y se pueden dividir en 3 grupos:
ScoMessageBusResultBoolean
ScoMessageBusResultInteger
ScoMessageBusResultNumeric
ScoMessageBusResultText
ScoMessageBusResultNone
(envuelve valores nulos)ScoMessageBusResultDelegated
que envuelve objetos y delega llamadas a propiedades y métodos al objeto subyacenteScoMessageBusResultCollection
y ScoMessageBusResultMap
que envuelven matrices indexadas por números (listas) y matrices indexadas por cadenas (mapas) e implementan las interfaces Countable
, ArrayAccess
y IteratorAggregate
También puede agregar sus propios objetos de valor de resultado personalizados extendiendo la clase abstracta ScoMessageBusResult
y devolviéndolos en el controlador apropiado.
La biblioteca sigue el estándar PSR-12.