Familièrement plus connu sous le nom de modèle command bus , mais la bibliothèque fait une distinction entre les commandes et les requêtes et vous permet d'appliquer des valeurs sans retour dans les gestionnaires de commandes pour vous maintenir en conformité avec le modèle CQRS.
Il s'agit d'une bibliothèque autonome , les deux seules dépendances étant les interfaces PSR-11 Container et PSR-3 Log pour permettre une meilleure interopérabilité.
Table des matières:
Installez la bibliothèque à l'aide de Composer :
composer require sco/message-bus
Vous devrez suivre la norme de chargement automatique PSR-4 et soit créer votre propre classe Service Container, ce qui consiste à implémenter PsrContainerContainerInterface
et peut être aussi simple que ce que la bibliothèque utilise pour sa suite de tests ScoMessageBusTestsStubContainerInMemoryContainer
, ou vous pouvez composer une bibliothèque de conteneurs de services qui adhère à la norme PSR-11 comme PHP-DI.
require ' vendor/autoload.php '
$ container = new InMemoryContainer( $ services )
$ bus = new Sco MessageBus Bus ( $ container );
$ bus -> dispatch ( new FindPostByIdQuery ( 1 ))
Nous pouvons utiliser ici deux approches, décorer la classe Bus fournie par la bibliothèque, ou injecter le Service Locator. Pour plus d'informations, vous pouvez lire Symfony Docs
Nous pouvons créer une nouvelle classe Decorator qui implémentera l'interface 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
];
}
}
Avec cette approche, tous les gestionnaires de votre application devront être ajoutés au tableau renvoyé par getSubscribedServices
, puisque les services de Symfony ne sont pas publics par défaut, et ils ne devraient vraiment pas l'être, donc à moins que vous n'ajoutiez vos gestionnaires à ce tableau lorsque le mappeur une fois le mappage terminé, il ne pourra pas trouver le gestionnaire et une exception de conteneur de service introuvable sera levée.
Une approche différente consisterait à injecter un localisateur de services avec tous les gestionnaires dans le bus de la bibliothèque. Cela serait fait dans les fichiers yaml d'enregistrement du service.
Localisateur de services anonyme :
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two '
Définition explicite du localisateur de service :
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 '
Développons ces configurations et utilisons la fonctionnalité tags du conteneur de services Symfony pour ajouter automatiquement des gestionnaires au bus :
En utilisant !tagged_locator
:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handler
Définition explicite du localisateur de service :
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 '
Pour l'utiliser efficacement avec le framework Laravel, tout ce que vous avez à faire est d'enregistrer le Bus dans le conteneur de services de Laravel et de fournir le conteneur comme argument à la classe Bus de la bibliothèque :
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
});
Chaque commande ou requête et leur combinaison d'objets de résultat respective se verront attribuer une identité unique, par exemple une commande, et son objet de résultat respectif aura l'identité de 00000001
. Cela peut être utile à des fins de journalisation, d’audit ou de débogage.
La stratégie de génération d'identité par défaut est un simple générateur ScoMessageBusIdentityRandomString
pour minimiser les dépendances externes. Pour utiliser autre chose, vous pouvez avoir besoin d'une bibliothèque comme https://github.com/ramsey/uuid et implémenter le ScoMessageBusIdentity
.
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}
FindPostByIdQuery
sera mappé à FindPostByIdHandler
ou un SavePostCommand
sera mappé à SavePostHandler
.#[IsCommand(handler: SavePostHandler::class)]
ou #[IsQuery(handler: FindPostByIdHandler::class)]
à votre classe Command/Query. Le nom du paramètre handler
peut être omis, cela dépend de vos préférences personnelles.ScoMessageBusMapper
.Chaque commande passera par une chaîne de Middlewares. Par défaut, la chaîne est vide, mais la bibliothèque propose du Middleware prêt à l'emploi :
begin
, commit
et rollback
sont de simples objets Closure
, vous pouvez donc utiliser l'approche ORM ou Persistence que vous préférez. Pour créer votre propre middleware personnalisé, vous devez implémenter l'interface ScoMessageBusMiddleware
et la fournir au 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 vous ajoutez ScoMessageBusMiddlewareEventMiddleware
vous pourrez vous abonner aux événements suivants :
MessageReceivedEvent - déclenché lorsque le message est reçu mais avant d'être traité.
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 : déclenché une fois que le message a été traité avec succès.
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 - déclenché lorsque la gestion des messages échoue et qu'une exception est levée.
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 accepte trois arguments de fonction, chacun pour chaque étape de la transaction : début, validation et annulation. Adopter cette approche vous permet d'utiliser n'importe quel ORM de votre choix ou même d'utiliser l'objet natif PDO pour interagir avec votre couche de persistance.
$ 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 bibliothèque encapsule les valeurs de retour du gestionnaire dans des objets de valeur de résultat pour fournir une API cohérente et afin que vous puissiez compter sur des valeurs de retour toujours du même type.
Tous les objets de valeur de résultat étendent la classe abstraite ScoMessageBusResult
et peuvent être divisés en 3 groupes :
ScoMessageBusResultBoolean
ScoMessageBusResultInteger
ScoMessageBusResultNumeric
ScoMessageBusResultText
ScoMessageBusResultNone
(encapsule les valeurs nulles)ScoMessageBusResultDelegated
qui encapsule les objets et délègue les appels aux propriétés et méthodes à l'objet sous-jacentScoMessageBusResultCollection
et ScoMessageBusResultMap
qui encapsulent des tableaux indexés par nombres (listes) et des tableaux indexés par chaînes (cartes) et implémentent les interfaces Countable
, ArrayAccess
et IteratorAggregate
Vous pouvez également ajouter vos propres objets de valeur de résultat personnalisés en étendant la classe abstraite ScoMessageBusResult
et en les renvoyant dans le gestionnaire approprié.
La bibliothèque suit la norme PSR-12.