CQRS - Command Query Responsibility Segregation
Introduction​
CQRS (Command Query Responsibility Segregation) est un pattern architectural qui sépare les opérations de lecture (Queries) des opérations d'écriture (Commands).
| Type | Description | Exemple |
|---|---|---|
| Command | Modifie l'état du système | Créer, modifier, supprimer |
| Query | Lit les données sans modification | Lister, afficher, rechercher |
CQRS est utile pour les systèmes complexes où les besoins en lecture et écriture diffèrent. Pour les applications simples, ce pattern peut ajouter une complexité inutile.
Installation​
composer require bowphp/cqrs
Commands​
Les Commands représentent des intentions de modification de l'état du système.
Créer une Command​
namespace App\Commands;
use Bow\CQRS\Command\CommandInterface;
class CreateUserCommand implements CommandInterface
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly string $password
) {}
}
Créer un Handler​
namespace App\CommandHandlers;
use App\Commands\CreateUserCommand;
use App\Models\User;
use App\Services\UserService;
use Bow\CQRS\Command\CommandHandlerInterface;
use Bow\CQRS\Command\CommandInterface;
use Bow\Security\Hash;
class CreateUserCommandHandler implements CommandHandlerInterface
{
public function __construct(
private UserService $userService
) {}
public function process(CommandInterface $command): mixed
{
if ($this->userService->emailExists($command->email)) {
throw new \RuntimeException("L'email existe déjà ");
}
return User::create([
'name' => $command->name,
'email' => $command->email,
'password' => Hash::make($command->password),
]);
}
}
Enregistrer le Handler​
namespace App\Configurations;
use App\Commands\CreateUserCommand;
use App\CommandHandlers\CreateUserCommandHandler;
use Bow\Configuration\Configuration;
use Bow\CQRS\Registration as CQRSRegistration;
class ApplicationConfiguration extends Configuration
{
public function run(): void
{
CQRSRegistration::commands([
CreateUserCommand::class => CreateUserCommandHandler::class,
]);
}
}
Exécuter la Command​
namespace App\Controllers;
use App\Commands\CreateUserCommand;
use Bow\CQRS\Command\CommandBus;
use Bow\Http\Request;
class UserController
{
public function __construct(
private CommandBus $commandBus
) {}
public function store(Request $request)
{
$command = new CreateUserCommand(
name: $request->get('name'),
email: $request->get('email'),
password: $request->get('password')
);
$user = $this->commandBus->execute($command);
return redirect('/users')->withFlash('message', 'Utilisateur créé');
}
}
Queries​
Les Queries récupèrent des données sans modifier l'état du système.
Créer une Query​
namespace App\Queries;
use Bow\CQRS\Query\QueryInterface;
class GetUserByIdQuery implements QueryInterface
{
public function __construct(
public readonly int $userId
) {}
}
Créer un Handler​
namespace App\QueryHandlers;
use App\Models\User;
use App\Queries\GetUserByIdQuery;
use Bow\CQRS\Query\QueryHandlerInterface;
use Bow\CQRS\Query\QueryInterface;
class GetUserByIdQueryHandler implements QueryHandlerInterface
{
public function process(QueryInterface $query): mixed
{
return User::find($query->userId);
}
}
Enregistrer le Handler​
use App\Queries\GetUserByIdQuery;
use App\QueryHandlers\GetUserByIdQueryHandler;
use Bow\CQRS\Registration as CQRSRegistration;
public function run(): void
{
CQRSRegistration::queries([
GetUserByIdQuery::class => GetUserByIdQueryHandler::class,
]);
}
Exécuter la Query​
namespace App\Controllers;
use App\Queries\GetUserByIdQuery;
use Bow\CQRS\Query\QueryBus;
class UserController
{
public function __construct(
private QueryBus $queryBus
) {}
public function show(int $id)
{
$query = new GetUserByIdQuery($id);
$user = $this->queryBus->execute($query);
return view('users/show', compact('user'));
}
}
Attributs PHP​
Utilisez les attributs PHP 8 pour lier automatiquement les handlers.
CommandHandler Attribute​
namespace App\CommandHandlers;
use App\Commands\CreateUserCommand;
use Bow\CQRS\Attribute\CommandHandler;
use Bow\CQRS\Command\CommandHandlerInterface;
use Bow\CQRS\Command\CommandInterface;
#[CommandHandler(CreateUserCommand::class)]
class CreateUserCommandHandler implements CommandHandlerInterface
{
public function process(CommandInterface $command): mixed
{
// Logique de création
}
}
QueryHandler Attribute​
namespace App\QueryHandlers;
use App\Queries\GetUserByIdQuery;
use Bow\CQRS\Attribute\QueryHandler;
use Bow\CQRS\Query\QueryHandlerInterface;
use Bow\CQRS\Query\QueryInterface;
#[QueryHandler(GetUserByIdQuery::class)]
class GetUserByIdQueryHandler implements QueryHandlerInterface
{
public function process(QueryInterface $query): mixed
{
// Logique de récupération
}
}
Enregistrement avec handlers()​
use App\CommandHandlers\CreateUserCommandHandler;
use App\QueryHandlers\GetUserByIdQueryHandler;
use Bow\CQRS\Registration as CQRSRegistration;
public function run(): void
{
CQRSRegistration::handlers([
CreateUserCommandHandler::class,
GetUserByIdQueryHandler::class,
]);
}
Routes​
// Commands (POST, PUT, DELETE)
$app->post('/users', 'UserController::store');
$app->put('/users/{id}', 'UserController::update');
$app->delete('/users/{id}', 'UserController::destroy');
// Queries (GET)
$app->get('/users', 'UserController::index');
$app->get('/users/{id}', 'UserController::show');
Structure recommandée​
app/
├── Commands/
│ ├── CreateUserCommand.php
│ └── UpdateUserCommand.php
├── CommandHandlers/
│ ├── CreateUserCommandHandler.php
│ └── UpdateUserCommandHandler.php
├── Queries/
│ ├── GetAllUsersQuery.php
│ └── GetUserByIdQuery.php
├── QueryHandlers/
│ ├── GetAllUsersQueryHandler.php
│ └── GetUserByIdQueryHandler.php
└── Controllers/
└── UserController.php
Interfaces​
| Interface | Méthode | Description |
|---|---|---|
CommandInterface | - | Marqueur pour les commands |
CommandHandlerInterface | process(CommandInterface): mixed | Traite une command |
QueryInterface | - | Marqueur pour les queries |
QueryHandlerInterface | process(QueryInterface): mixed | Traite une query |
Bus​
| Classe | Méthode | Description |
|---|---|---|
CommandBus | execute(CommandInterface): mixed | Exécute une command |
QueryBus | execute(QueryInterface): mixed | Exécute une query |
Il manque quelque chose ?
Si vous rencontrez des problèmes avec la documentation ou si vous avez des suggestions pour améliorer la documentation ou le projet en général, veuillez déposer une issue pour nous, ou envoyer un tweet mentionnant le compte Twitter @bowframework ou sur directement sur le github.