Explorez le CQRS avec le package artisansdk

Publié le 31 octobre 2023 par Mathieu De Gracia
Couverture de l'article Explorez le CQRS avec le package artisansdk

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
Mathieu De Gracia avatar
Mathieu De Gracia
Des fois, mon chat code à ma place 🐱

A lire

Autres articles de la même catégorie