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_handler
Explizite 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:
ScoMessageBusResultBoolean
ScoMessageBusResultInteger
ScoMessageBusResultNumeric
ScoMessageBusResultText
ScoMessageBusResultNone
(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.