Umgangssprachlich eher als command bus bekannt, aber die Bibliothek unterscheidet zwischen Befehlen und Abfragen und ermöglicht es Ihnen, in Befehlshandlern keine Rückgabewerte zu erzwingen, um mit dem CQRS-Muster Schritt zu halten.
Dies ist eine eigenständige Bibliothek . Die einzigen beiden Abhängigkeiten sind die Schnittstellen PSR-11 Container und PSR-3 Log, um eine bessere Interoperabilität zu ermöglichen.
Inhaltsverzeichnis:
Installieren Sie die Bibliothek mit Composer:
composer require sco/message-bus Sie müssen dem PSR-4-Autoloading-Standard folgen und entweder Ihre eigene Service-Container-Klasse erstellen, was eine Frage der Implementierung von PsrContainerContainerInterface ist und so einfach sein kann wie das, was die Bibliothek für ihre Testsuite ScoMessageBusTestsStubContainerInMemoryContainer verwendet ScoMessageBusTestsStubContainerInMemoryContainer , oder Sie können Composer eine Service-Container-Bibliothek anfordern, die dem PSR-11-Standard wie PHP-DI entspricht.
require ' vendor/autoload.php '
$ container = new InMemoryContainer( $ services )
$ bus = new Sco MessageBus Bus ( $ container );
$ bus -> dispatch ( new FindPostByIdQuery ( 1 ))Wir können hier zwei Ansätze verwenden: die von der Bibliothek bereitgestellte Bus-Klasse dekorieren oder den Service Locator einfügen. Weitere Informationen finden Sie in den Symfony-Dokumenten
Wir können eine neue Decorator-Klasse erstellen, die SymfonyContractsServiceServiceSubscriberInterface Schnittstelle von Symfony implementiert:
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
];
}
} Bei diesem Ansatz müssen alle Handler in Ihrer Anwendung dem von getSubscribedServices zurückgegebenen Array hinzugefügt werden, da Dienste in Symfony standardmäßig nicht öffentlich sind, und das sollten sie auch nicht sein, es sei denn, Sie fügen Ihre Handler diesem Array hinzu, wenn der Mapper aktiviert ist Wenn die Zuordnung abgeschlossen ist, kann der Handler nicht gefunden werden, und es wird eine Ausnahme für den Container „Dienst nicht gefunden“ ausgelöst.
Ein anderer Ansatz wäre, einen Service Locator mit allen Handlern in den Bus der Bibliothek einzuspeisen. Dies würde in den Yaml-Dateien zur Dienstregistrierung erfolgen.
Anonymer Service-Locator:
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two 'Explizite Service-Locator-Definition:
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 'Erweitern wir diese Konfigurationen und verwenden wir die Tags-Funktion des Symfony-Service-Containers, um Handler automatisch zum Bus hinzuzufügen:
Mit !tagged_locator :
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handlerExplizite Service-Locator-Definition:
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 'Um es effektiv mit dem Laravel-Framework zu nutzen, müssen Sie lediglich den Bus im Service-Container von Laravel registrieren und den Container als Argument für die Bus-Klasse der Bibliothek bereitstellen:
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
}); Jedem Befehl oder jeder Abfrage und ihrer jeweiligen Kombination aus Ergebnisobjekten wird eine eindeutige Identität zugewiesen, z. B. ein Befehl, und ihr jeweiliges Ergebnisobjekt hat die Identität 00000001 . Dies kann für Protokollierungs-, Überwachungs- oder Debugging-Zwecke nützlich sein.
Die Standardstrategie zur Identitätsgenerierung ist ein einfacher ScoMessageBusIdentityRandomString Generator, um die externen Abhängigkeiten auf ein Minimum zu beschränken. Um etwas anderes zu verwenden, benötigen Sie möglicherweise eine Bibliothek wie https://github.com/ramsey/uuid und implementieren ScoMessageBusIdentity .
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}FindPostByIdQuery FindPostByIdHandler oder ein SavePostCommand SavePostHandler zugeordnet.#[IsCommand(handler: SavePostHandler::class)] oder #[IsQuery(handler: FindPostByIdHandler::class)] zu Ihrer Command/Query-Klasse hinzu. Der Name handler Parameters kann weggelassen werden, es liegt an Ihren persönlichen Vorlieben.ScoMessageBusMapper Schnittstelle implementieren.Jeder Befehl wird durch eine Kette von Middlewares weitergeleitet. Standardmäßig ist die Kette leer, aber die Bibliothek bietet einige sofort einsatzbereite Middleware:
begin , commit und rollback sind einfache Closure -Objekte, sodass Sie den von Ihnen bevorzugten ORM- oder Persistenzansatz verwenden können. Um Ihre eigene benutzerdefinierte Middleware zu erstellen, müssen Sie die ScoMessageBusMiddleware -Schnittstelle implementieren und sie dem Bus bereitstellen:
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 ()]); Wenn Sie ScoMessageBusMiddlewareEventMiddleware hinzufügen, können Sie die folgenden Ereignisse abonnieren:
MessageReceivedEvent – wird ausgelöst, wenn die Nachricht empfangen wird, aber bevor sie verarbeitet wird.
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 – wird ausgelöst, nachdem die Nachricht erfolgreich verarbeitet wurde.
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 – wird ausgelöst, wenn die Nachrichtenverarbeitung fehlschlägt und eine Ausnahme ausgelöst wird.
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
});Die Transaktions-Middleware akzeptiert drei Funktionsargumente, jeweils für jede Phase der Transaktion: Beginn, Commit und Rollback. Wenn Sie diesen Ansatz wählen, können Sie jedes beliebige ORM verwenden oder sogar das native PDO-Objekt verwenden, um mit Ihrer Persistenzschicht zu interagieren.
$ 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 (),
);Die Bibliothek verpackt die Handler-Rückgabewerte in Ergebniswertobjekte, um eine konsistente API bereitzustellen und Sie sich darauf verlassen zu können, dass die Rückgabewerte immer vom gleichen Typ sind.
Alle Ergebniswertobjekte erweitern die abstrakte Klasse ScoMessageBusResult und können in drei Gruppen unterteilt werden:
ScoMessageBusResultBooleanScoMessageBusResultIntegerScoMessageBusResultNumericScoMessageBusResultTextScoMessageBusResultNone (umschließt Nullwerte)ScoMessageBusResultDelegated das Objekte umschließt und Aufrufe von Eigenschaften und Methoden an das zugrunde liegende Objekt delegiertScoMessageBusResultCollection und ScoMessageBusResultMap die zahlenindizierte Arrays (Listen) und stringindizierte Arrays (Maps) umschließen und die Schnittstellen Countable , ArrayAccess und IteratorAggregate implementieren Sie können auch Ihre eigenen benutzerdefinierten Ergebniswertobjekte hinzufügen, indem Sie die abstrakte Klasse ScoMessageBusResult erweitern und sie im entsprechenden Handler zurückgeben.
Die Bibliothek folgt dem PSR-12-Standard.