Nous vous parlions récemment du pattern CQRS et de comment l'intégrer dans une application Laravel.
Afin d'approfondir notre utilisation du pattern, intéressons-nous aujourd'hui au package artisansdk/cqrs fournissant un command bus entièrement fonctionnel et riche de plusieurs fonctionnalités intéressantes.
Nous ne reviendrons pas ici sur les avantages et les inconvénients du pattern CQRS, la lecture de notre article sur les bases du pattern est vivement encouragée si vous découvrez le pattern.
Installation
Le package s'installe directement avec composer, une version 7 de PHP et 5 de Laravel seront aux minimums nécessaires :
1composer require artisansdk/cqrs
Le package ne possède pas de configuration à publier.
La commande
Nous allons intégrer une commande similaire à celle présentée dans notre article sur les fondamentaux du CQRS, cette dernière aura pour simple responsabilité de créer un nouveau modèle Post :
1 2namespace App\Commands; 3 4use App\Models\Post; 5use ArtisanSdk\CQRS\Command; 6 7class CreatePost extends Command 8{ 9 public function run()10 {11 $post = Post::create([12 'title' => $this->argument('title'),13 'highlight' => $this->argument('highlight'),14 'content' => $this->argument('content'),15 ]);16 17 return $post;18 }19}
Une commande doit impérativement étendre la class ArtisanSdk\CQRS\Command
et implémenter une méthode run
contenant les actions à réaliser lors de son exécution.
Maintenant, imaginons un controller voulant exécuter notre command CreatePost
.
Le package ArtisanSdk
fourni un dispatcher qui fera office de command bus, ce dernier propose plusieurs fonctionnalités intéressantes que nous allons voir par la suite.
1namespace App\Http\Controllers; 2 3use Illuminate\Http\Request; 4use App\Commands\CreatePost; 5use ArtisanSdk\CQRS\Dispatcher; 6 7class PostController extends Controller 8{ 9 public function __construct(10 private Dispatcher $dispatcher,11 ) {}12 13 public function store(Request $request)14 {15 $command = $this->dispatcher->command(CreatePost::class);16 17 $command->arguments([18 'title' => $request->get('title'),19 'highlight' => $request->get('highlight'),20 'content' => $request->get('content'),21 ]);22 23 $post = $command->run();24 }25}
Une fois votre commande instanciée à l'aide du dispatcher, la méthode argument
permettra de lui transmettre des propriétés, pour finir, la méthode run
exécutera la commande.
Les événements
Le package facilite le lancement de différents événements avant et après l'exécution de vos commandes depuis le command bus.
Pour les activer, rien de plus simple, il vous suffira d'ajouter l'interface Eventable
à votre commande ainsi qu'implémenter les méthodes beforeEvent
et afterEvent
, le command bus se chargera ensuite d'exécuter les événements associés aux moments opportuns.
1 2namespace App\Commands; 3 4use App\Models\Post; 5use App\Events\PostSaved; 6use ArtisanSdk\CQRS\Command; 7use ArtisanSdk\Contract\Eventable; 8 9class CreatePost extends Command implements Eventable 10{11 public function run()12 {13 $post = Post::create([14 'title' => $this->argument('title'),15 'highlight' => $this->argument('highlight'),16 'content' => $this->argument('content'),17 ]);18 19 return $post;20 }21 22 public function beforeEvent(): string 23 { 24 return PostCreating::class; 25 } 26 27 public function afterEvent(): string 28 { 29 return PostCreated::class; 30 } 31}
Les événements exécutés par une commande du package ArtisanSdk
seront quelque peu différents des classiques events de Laravel.
Pour être pleinement fonctionnels, les events de notre package devront implémenter la classe ArtisanSdk\CQRS\Events\Event
:
1namespace App\Events; 2 3use App\Models\Post; 4use ArtisanSdk\CQRS\Events\Event; 5 6class PostCreated extends Event 7{ 8 public function __construct( 9 public Post $post,10 ) {}11}
Hormis ce détail concernant là class à étendre, l'événement sera on ne peut plus classique et nécessitera d'être défini dans le event listener de votre application Laravel depuis son App\Providers\EventServiceProvider
:
1class EventServiceProvider extends ServiceProvider 2{ 3 protected $listen = [ 4 PostCreating::class => [ 5 // 6 ], 7 PostCreated::class => [ 8 // 9 ],10 ];11 12 // [...]13}
Les transactions
À l'instar des événements, exécuter vos commandes à l'intérieur d'une transaction sera tout aussi simple, nécessitant uniquement d'ajouter l'interface Transactional
à votre commande !
1namespace App\Commands; 2 3use App\Models\Post; 4use ArtisanSdk\CQRS\Command; 5use ArtisanSdk\Contract\Transactional; 6 7class CreatePost extends Command implements Transactional 8{ 9 public function run()10 {11 $post = Post::create([12 'title' => $this->argument('title'),13 'highlight' => $this->argument('highlight'),14 'content' => $this->argument('content'),15 ]);16 17 return $post;18 }19}
Une transaction s'assure du bon fonctionnement du code avant d'effectuer concrètement les requêtes en base de données.
Dès lors, l'intégralité de la méthode run
sera englobée dans une transaction qui procédera à un rollback si la commande lève une exception.
La validation
Le package ne proposant pas directement l'utilisation de DTO pour garantir la pertinence des propriétés d'une commande, il offrira néanmoins une intégration simplifiée de la classe Validator de Laravel dans vos commandes :
1namespace App\Commands; 2 3use App\Models\Post; 4use ArtisanSdk\CQRS\Command; 5 6class CreatePost extends Command 7{ 8 public function run() 9 {10 $title = $this->argument('title', ['string', 'min:10']); 11 12 $post = Post::create([13 'title' => $title,14 'highlight' => $this->argument('highlight'),15 'content' => $this->argument('content'),16 ]);17 18 return $post;19 }20}
Vous aurez ainsi accès à l'intégralité des fonctionnalités du Validator de Laravel qui seront amplement suffisantes pour s'assurer de la pertinence des propriétés transmises à la commande.
D'autres façons d'interagir avec le Validator sont disponibles que vous trouverez dans ce chapitre de la documentation du package.
Abort
Parmi les petites fonctionnalités intéressantes du package, la méthode abort
vous permettra de préciser à votre command bus qu'une commande a été stoppée durant son exécution.
Depuis le run
d'une commande, exécutez simplement la méthode abort
suivante :
1namespace App\Commands; 2 3use ArtisanSdk\CQRS\Command; 4 5class CreatePost extends Command 6{ 7 public function run() 8 { 9 $this->abort(); 10 11 return; 12 }13}
L'instance de la commande sera désormais taguée comme étant "avortée" :
1use ArtisanSdk\CQRS\Dispatcher;2 3$command = app(Dispatcher::class)->command(CreatePost::class);4 5$command->run();6 7$command->toBase()->aborted(); // true
A lire
Autres articles de la même catégorie
Laravel Arkitect
Appliquez des règles d'architectures dans vos projets grace à Arkitect !
Mathieu De Gracia
Traiter du Markdown avec league/commonmark
Découvrons ensemble comment ce paquet nous fait gagner beaucoup de temps lors de l’écriture de nos articles !
William Suppo
Maîtriser vos données avec un DTO !
Analyse du paquet data-transfer-object de Spatie qui permet, à travers une entité, de rendre notre code plus consistant.
William Suppo