Command Bus Pattern
- Type : Architectural
- Difficulté : 7/10
- Définition succincte : Le Command Bus Pattern est un design pattern architectural où des commandes sont envoyées à un bus (un composant central) qui gère l'exécution de ces commandes. Chaque commande encapsule une action ou une intention claire, et le bus est responsable de la délégation de cette commande à un handler approprié. Ce pattern permet de centraliser l'exécution des actions et de rendre le code plus modulaire et flexible.
- Contexte spécifique à Laravel : En Laravel, ce pattern est implémenté par le biais de composants tels que les queues, les events, et les commands. Laravel dispose d'une Command Bus native qui permet d'envoyer des commandes à travers le framework via le système de bus intégré.
Objectif du Command Bus Pattern
L'objectif principal du Command Bus Pattern est de centraliser l'exécution des commandes dans une application. Ce pattern est particulièrement utile dans les systèmes où les actions doivent être traitées de manière asynchrone, en file d'attente, ou par différents gestionnaires, tout en maintenant un faible couplage entre les composants.
Structure du Command Bus Pattern
- Command : Représente une action ou une intention (comme créer un utilisateur, générer un rapport, etc.).
- Handler : Un composant qui est responsable d'exécuter une commande spécifique.
- Command Bus : Le composant central qui reçoit les commandes et les délègue aux handlers appropriés.
- Dispatcher : Le mécanisme qui envoie la commande au bus.
1. Exemple d'utilisation dans Laravel
Laravel intègre un Command Bus qui permet d'envoyer et de gérer des commandes via son système de queue, events, ou jobs. Voici un exemple simple où une commande est envoyée via le Bus pour créer un utilisateur.
a) Créer la commande
// app/Jobs/CreateUser.php
namespace App\Jobs;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class CreateUser implements ShouldQueue
{
use Queueable;
protected $name;
protected $email;
public function __construct($name, $email)
{
$this->name = $name;
$this->email = $email;
}
public function handle()
{
User::create([
'name' => $this->name,
'email' => $this->email,
]);
}
}
b) Envoyer la commande via le Bus
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Jobs\CreateUser;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function store(Request $request)
{
// Envoyer la commande via le Command Bus
CreateUser::dispatch($request->input('name'), $request->input('email'));
return response()->json(['message' => 'User creation initiated']);
}
}
Dans cet exemple, la commande CreateUser est envoyée via le bus et exécutée de manière asynchrone, potentiellement dans une file d'attente, sans que le contrôleur ne gère directement la logique métier.
2. Implémentation complète dans Laravel
Imaginons un système où tu veux gérer un flux de commandes pour des tâches administratives. Par exemple, tu souhaites créer une commande pour générer un rapport de vente et une autre pour envoyer des notifications. Le Command Bus Pattern te permet de centraliser la gestion de ces commandes, facilitant leur ajout, suppression ou modification.
a) Créer la commande GenerateSalesReport
Voici une commande qui encapsule l'intention de générer un rapport de ventes.
// app/Commands/GenerateSalesReport.php
namespace App\Commands;
class GenerateSalesReport
{
protected $startDate;
protected $endDate;
public function __construct($startDate, $endDate)
{
$this->startDate = $startDate;
$this->endDate = $endDate;
}
public function getStartDate()
{
return $this->startDate;
}
public function getEndDate()
{
return $this->endDate;
}
}
b) Créer le handler GenerateSalesReportHandler
Le handler va être responsable de la logique métier pour générer le rapport de ventes en fonction de la commande reçue.
// app/Handlers/GenerateSalesReportHandler.php
namespace App\Handlers;
use App\Commands\GenerateSalesReport;
class GenerateSalesReportHandler
{
public function handle(GenerateSalesReport $command)
{
$startDate = $command->getStartDate();
$endDate = $command->getEndDate();
// Logique pour générer un rapport de ventes (exemple simplifié)
echo "Generating sales report from {$startDate} to {$endDate}..." . PHP_EOL;
// En réalité, tu pourrais ici générer un fichier PDF ou CSV
}
}
c) Créer la commande SendNotification
Voici une deuxième commande pour envoyer des notifications.
// app/Commands/SendNotification.php
namespace App\Commands;
class SendNotification
{
protected $recipient;
protected $message;
public function __construct($recipient, $message)
{
$this->recipient = $recipient;
$this->message = $message;
}
public function getRecipient()
{
return $this->recipient;
}
public function getMessage()
{
return $this->message;
}
}
d) Créer le handler SendNotificationHandler
Le handler est responsable de la logique d'envoi de notifications.
// app/Handlers/SendNotificationHandler.php
namespace App\Handlers;
use App\Commands\SendNotification;
class SendNotificationHandler
{
public function handle(SendNotification $command)
{
$recipient = $command->getRecipient();
$message = $command->getMessage();
// Logique pour envoyer une notification (exemple simplifié)
echo "Sending notification to {$recipient}: {$message}" . PHP_EOL;
// En réalité, tu pourrais utiliser un service comme Mail ou SMS ici
}
}
e) Créer un Command Bus
Nous allons maintenant créer un simple Command Bus pour gérer les commandes et les envoyer aux handlers appropriés.
// app/Services/CommandBus.php
namespace App\Services;
class CommandBus
{
protected $handlers = [];
// Enregistrer un handler pour une commande spécifique
public function register($commandClass, $handler)
{
$this->handlers[$commandClass] = $handler;
}
// Exécuter la commande en appelant son handler
public function execute($command)
{
$commandClass = get_class($command);
if (isset($this->handlers[$commandClass])) {
$handler = $this->handlers[$commandClass];
return $handler->handle($command);
}
throw new \Exception("No handler registered for command: {$commandClass}");
}
}
f) Utiliser le Command Bus dans une commande CLI
Nous allons maintenant créer une commande CLI pour utiliser le Command Bus et exécuter les commandes GenerateSalesReport et SendNotification.
// app/Console/Commands/ExecuteCommands.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\CommandBus;
use App\Commands\GenerateSalesReport;
use App\Commands\SendNotification;
use App\Handlers\GenerateSalesReportHandler;
use App\Handlers\SendNotificationHandler;
class ExecuteCommands extends Command
{
protected $signature = 'commands:execute';
protected $description = 'Exécuter des commandes via le Command Bus';
public function handle()
{
// Créer le bus
$commandBus = new CommandBus();
// Enregistrer les handlers
$commandBus->register(GenerateSalesReport::class, new GenerateSalesReportHandler());
$commandBus->register(SendNotification::class, new SendNotificationHandler());
// Créer et exécuter les commandes
$salesReportCommand = new GenerateSalesReport('2023-01-01', '2023-12-31');
$commandBus->execute($salesReportCommand);
$notificationCommand = new SendNotification('admin@example.com', 'Votre rapport est prêt');
$commandBus->execute($notificationCommand);
}
}
g) Exécution de la commande
Tu peux exécuter cette commande pour voir comment le Command Bus gère l'exécution des commandes :
php artisan commands:execute
Résultat attendu :
Generating sales report from 2023-01-01 to 2023-12-31...
Sending notification to admin@example.com: Votre rapport est prêt
Avantages du Command Bus Pattern
- Séparation des préoccupations : Les commandes encapsulent des actions spécifiques, tandis que les handlers contiennent la logique métier, ce qui permet un meilleur découplage du code.
- Modularité : Les commandes peuvent être facilement ajoutées, supprimées ou modifiées sans affecter le reste du système.
- Réutilisabilité : Le même **
Command Bus** peut gérer une variété de commandes, ce qui rend le système extensible.
- Gestion asynchrone : En combinant avec les queues Laravel, les commandes peuvent être exécutées de manière asynchrone.
Inconvénients du Command Bus Pattern
- Complexité accrue : Ce pattern introduit plusieurs classes (commandes, handlers, bus), ce qui peut ajouter de la complexité à des systèmes simples.
- Surcharge de code : Pour des actions triviales, l'utilisation du Command Bus peut entraîner une surabondance de code.
- Performances : Dans certains cas, l'exécution de nombreuses commandes peut affecter les performances du système si elles ne sont pas optimisées.
Conclusion
Le Command Bus Pattern est particulièrement utile dans des applications Laravel qui doivent gérer de nombreuses actions asynchrones ou modulaires. En encapsulant la logique métier dans des commandes distinctes, ce pattern permet de garder le code organisé, flexible et facilement testable. Laravel intègre nativement ce pattern avec son Bus pour les queues, événements, et jobs, mais il peut aussi être implémenté manuellement pour des cas plus spécifiques.