Dalam bahasa sehari-hari lebih dikenal sebagai pola command bus , namun pustaka membuat perbedaan antara Perintah dan Kueri dan memungkinkan Anda menerapkan nilai tanpa pengembalian di Pengendali Perintah agar Anda tetap sejalan dengan pola CQRS.
Ini adalah perpustakaan yang berdiri sendiri , dengan dua dependensi saja yaitu antarmuka Kontainer PSR-11 dan Log PSR-3 untuk memungkinkan interoperabilitas yang lebih baik.
Daftar isi:
Instal perpustakaan menggunakan komposer:
composer require sco/message-bus
Anda harus mengikuti standar pemuatan otomatis PSR-4 dan membuat kelas Service Container Anda sendiri, yang merupakan masalah penerapan PsrContainerContainerInterface
dan bisa sesederhana apa yang digunakan perpustakaan untuk rangkaian pengujiannya ScoMessageBusTestsStubContainerInMemoryContainer
, atau Anda dapat membuat komposer memerlukan pustaka Service Container yang mematuhi Standar PSR-11 seperti PHP-DI.
require ' vendor/autoload.php '
$ container = new InMemoryContainer( $ services )
$ bus = new Sco MessageBus Bus ( $ container );
$ bus -> dispatch ( new FindPostByIdQuery ( 1 ))
Kita dapat menggunakan dua pendekatan di sini, mendekorasi kelas Bus yang disediakan oleh perpustakaan, atau memasukkan Service Locator. Untuk info lebih lanjut Anda dapat membaca Symfony Docs
Kita dapat membuat kelas Dekorator baru yang akan mengimplementasikan antarmuka SymfonyContractsServiceServiceSubscriberInterface
:
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
];
}
}
Dengan pendekatan ini, semua penangan di aplikasi Anda harus ditambahkan ke larik yang dikembalikan oleh getSubscribedServices
, karena layanan di Symfony tidak bersifat publik secara default, dan memang tidak seharusnya demikian, jadi kecuali Anda menambahkan penangan ke larik ini saat pembuat peta selesai memetakan, ia tidak akan dapat menemukan pengendalinya dan pengecualian wadah layanan tidak ditemukan akan dilempar.
Pendekatan yang berbeda adalah dengan memasukkan Service Locator dengan semua penangan ke dalam Bus perpustakaan. Ini akan dilakukan di file yaml pendaftaran layanan.
Pencari layanan anonim:
services :
_defaults :
autowire : true
autoconfigure : true
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !service_locator
' @FindPostByIdHandler ' : ' handler_one '
' @SavePostHandler ' : ' handler_two '
Definisi pencari layanan eksplisit:
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 '
Mari perluas konfigurasi ini dan gunakan fitur tag pada wadah layanan Symfony untuk menambahkan penangan ke Bus secara otomatis:
Menggunakan !tagged_locator
:
services :
_defaults :
autowire : true
autoconfigure : true
_instanceof :
ScoMessageBusHandler :
tags : ['message_handler']
# Anonymous Service Locator
ScoMessageBusBus :
arguments :
$container : !tagged_locator message_handler
Definisi pencari layanan eksplisit:
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 '
Untuk menggunakannya secara efektif dengan framework Laravel, yang harus Anda lakukan adalah mendaftarkan Bus di Service Container Laravel dan menyediakan container tersebut sebagai argumen ke kelas Bus perpustakaan:
$ this -> app -> bind ( Sco MessageBus Bus::class, function ( $ app ) {
return new Sco MessageBus Bus ( $ app );
});
Setiap Komando atau Kueri dan kombo objek Hasil masing-masing akan diberi Identitas unik, misalnya Perintah, dan masing-masing objek Hasil akan memiliki identitas 00000001
. Ini dapat berguna untuk tujuan logging, audit, atau debugging.
Strategi pembuatan Identitas default adalah generator ScoMessageBusIdentityRandomString
sederhana untuk meminimalkan ketergantungan eksternal. Untuk menggunakan sesuatu yang lain, Anda memerlukan perpustakaan seperti https://github.com/ramsey/uuid dan mengimplementasikan ScoMessageBusIdentity
.
use Sco MessageBus Identity ;
class UuidIdentity implements Identity
{
public function generate () : string
{
return Uuid:: uuid7 ()-> toString ();
}
}
FindPostByIdQuery
akan dipetakan ke FindPostByIdHandler
atau SavePostCommand
akan dipetakan ke SavePostHandler
.#[IsCommand(handler: SavePostHandler::class)]
atau #[IsQuery(handler: FindPostByIdHandler::class)]
ke kelas Command/Query Anda. Nama parameter handler
dapat dihilangkan, terserah preferensi pribadi Anda.ScoMessageBusMapper
.Setiap perintah akan diteruskan melalui rantai Middlewares. Secara default, rantainya kosong, tetapi perpustakaan menawarkan beberapa Middleware yang siap digunakan:
begin
, commit
, dan rollback
adalah objek Closure
biasa, sehingga Anda dapat menggunakan pendekatan ORM atau Persistence mana pun yang Anda sukai. Untuk membuat middleware kustom Anda sendiri, Anda perlu mengimplementasikan antarmuka ScoMessageBusMiddleware
dan menyediakannya ke 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 ()]);
Jika Anda menambahkan ScoMessageBusMiddlewareEventMiddleware
Anda akan dapat berlangganan acara berikut:
MessageReceivedEvent - dimunculkan saat pesan diterima tetapi sebelum ditangani.
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 - dimunculkan setelah pesan berhasil ditangani.
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 - dimunculkan ketika penanganan pesan gagal dan pengecualian muncul.
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 menerima tiga argumen fungsi, masing-masing untuk setiap tahapan transaksi: mulai, komit, dan kembalikan. Menggunakan pendekatan ini memungkinkan Anda menggunakan ORM apa pun yang Anda sukai atau bahkan menggunakan objek PDO asli untuk berinteraksi dengan lapisan persistensi Anda.
$ 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 (),
);
Library menggabungkan nilai kembalian Handler ke dalam objek nilai Hasil untuk menyediakan API yang konsisten sehingga Anda dapat bergantung pada nilai kembalian yang selalu bertipe sama.
Semua objek nilai Result memperluas kelas abstrak ScoMessageBusResult
dan dapat dibagi menjadi 3 grup:
ScoMessageBusResultBoolean
ScoMessageBusResultInteger
ScoMessageBusResultNumeric
ScoMessageBusResultText
ScoMessageBusResultNone
(membungkus nilai null)ScoMessageBusResultDelegated
yang membungkus objek dan mendelegasikan panggilan ke properti dan metode ke objek yang mendasarinyaScoMessageBusResultCollection
dan ScoMessageBusResultMap
yang membungkus array yang diindeks nomor (daftar) dan array yang diindeks string (peta) dan mengimplementasikan antarmuka Countable
, ArrayAccess
dan IteratorAggregate
Anda juga dapat menambahkan objek nilai Hasil kustom Anda sendiri dengan memperluas kelas abstrak ScoMessageBusResult
dan mengembalikannya ke pengendali yang sesuai.
Perpustakaan mengikuti standar PSR-12.