口語的にはcommand busパターンとして知られていますが、ライブラリではコマンドとクエリを区別し、CQRS パターンに沿った状態を維持するためにコマンド ハンドラーで戻り値を強制しないようにできます。
これはスタンドアロン ライブラリであり、相互運用性を向上させるための依存関係は PSR-11 コンテナーと PSR-3 ログ インターフェイスの 2 つだけです。
目次:
Composer を使用してライブラリをインストールします。
composer require sco/message-busPSR-4 オートローディング標準に従い、独自のサービス コンテナ クラスを作成する必要があります。これはPsrContainerContainerInterface実装することであり、ライブラリがそのテスト スイートScoMessageBusTestsStubContainerInMemoryContainerに使用しているものと同じくらい単純にすることができます。 ScoMessageBusTestsStubContainerInMemoryContainer 、または、PHP-DI のような PSR-11 標準に準拠するサービス コンテナ ライブラリを作成者に要求することもできます。
require ' vendor/autoload.php '
$ container = new InMemoryContainer( $ services )
$ bus = new Sco MessageBus Bus ( $ container );
$ bus -> dispatch ( new FindPostByIdQuery ( 1 ))ここでは、ライブラリによって提供される Bus クラスを装飾するか、Service Locator を挿入するという 2 つのアプローチを使用できます。詳細については、Symfony のドキュメントを参照してください。
Symfony のSymfonyContractsServiceServiceSubscriberInterfaceインターフェイスを実装する新しい Decorator クラスを作成できます。
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
];
}
}このアプローチでは、アプリケーション内のすべてのハンドラーをgetSubscribedServicesによって返される配列に追加する必要があります。これは、Symfony のサービスはデフォルトではパブリックではなく、実際にパブリックであるべきではないためです。マッパーの作成時にハンドラーをこの配列に追加しない限り、マッピングが完了すると、ハンドラーを見つけることができなくなり、サービスが見つからないコンテナ例外がスローされます。
別のアプローチは、すべてのハンドラーを含む Service Locator をライブラリのバスに挿入することです。これはサービス登録 yaml ファイルで行われます。
匿名サービス ロケーター:
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two '明示的なサービス ロケーターの定義:
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 'これらの設定を拡張し、Symfony のサービスコンテナのタグ機能を使用して、バスにハンドラーを自動的に追加してみましょう。
!tagged_locatorの使用:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handler明示的なサービス ロケーターの定義:
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 'Laravelフレームワークでこれを効果的に使用するには、Laravelのサービスコンテナにバスを登録し、そのコンテナをライブラリのバスクラスの引数として提供するだけです。
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
});各コマンドまたはクエリとそれぞれのResultオブジェクトの組み合わせには、一意の ID (たとえばCommand) が割り当てられ、それぞれのResultオブジェクトの ID は00000001になります。これは、ログ記録、監査、またはデバッグの目的に役立ちます。
デフォルトの ID 生成戦略は、外部依存関係を最小限に抑えるための単純なScoMessageBusIdentityRandomStringジェネレーターです。他のものを使用するには、 https://github.com/ramsey/uuid のようなライブラリを必要とし、 ScoMessageBusIdentityを実装することができます。
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}FindPostByIdQuery FindPostByIdHandlerにマップされ、 SavePostCommand SavePostHandlerにマップされます。#[IsCommand(handler: SavePostHandler::class)]または#[IsQuery(handler: FindPostByIdHandler::class)]を Command/Query クラスに追加します。 handlerパラメーター名は個人の好みに応じて省略できます。ScoMessageBusMapperインターフェイスを実装することで作成できます。各コマンドはミドルウェアのチェーンを介して渡されます。デフォルトではチェーンは空ですが、ライブラリはすぐに使用できるいくつかのミドルウェアを提供します。
begin 、 commit 、およびrollbackステップはプレーンなClosureオブジェクトであるため、好みの ORM または Persistence アプローチを使用できます。独自のカスタム ミドルウェアを作成するにはScoMessageBusMiddlewareインターフェイスを実装し、それをバスに提供する必要があります。
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 ()]);ScoMessageBusMiddlewareEventMiddlewareを追加すると、次のイベントをサブスクライブできるようになります。
MessageReceivedEvent - メッセージが受信されたとき、処理される前に発生します。
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 - メッセージが正常に処理された後に発生します。
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 - メッセージ処理が失敗し、例外がスローされたときに発生します。
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
});トランザクション ミドルウェアは、トランザクションの各段階 (開始、コミット、ロールバック) ごとに 3 つの関数引数を受け入れます。このアプローチを採用すると、任意の ORM を使用したり、ネイティブの PDO オブジェクトを使用して永続化レイヤーと対話したりすることができます。
$ 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 (),
);ライブラリはハンドラーの戻り値を結果値オブジェクトにラップして一貫した API を提供し、戻り値が常に同じ型であることに依存できるようにします。
すべての Result 値オブジェクトはScoMessageBusResult抽象クラスを拡張し、次の 3 つのグループに分類できます。
ScoMessageBusResultBooleanScoMessageBusResultIntegerScoMessageBusResultNumericScoMessageBusResultTextScoMessageBusResultNone (null 値をラップ)ScoMessageBusResultDelegatedオブジェクトをラップし、プロパティとメソッドの呼び出しを基になるオブジェクトに委任します。ScoMessageBusResultCollectionおよびScoMessageBusResultMap 、数値インデックス付き配列 (リスト) と文字列インデックス付き配列 (マップ) をラップし、 Countable 、 ArrayAccess 、およびIteratorAggregateインターフェイスを実装します。抽象クラスScoMessageBusResultを拡張し、それらを適切なハンドラーで返すことにより、独自のカスタム Result 値オブジェクトを追加することもできます。
ライブラリは PSR-12 標準に従っています。