Aller au contenu principal
Version: 5.x

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).

TypeDescriptionExemple
CommandModifie l'état du systèmeCréer, modifier, supprimer
QueryLit les données sans modificationLister, afficher, rechercher
Quand utiliser CQRS ?

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​

app/Commands/CreateUserCommand.php
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​

app/CommandHandlers/CreateUserCommandHandler.php
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​

app/Configurations/ApplicationConfiguration.php
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​

app/Controllers/UserController.php
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​

app/Queries/GetUserByIdQuery.php
namespace App\Queries;

use Bow\CQRS\Query\QueryInterface;

class GetUserByIdQuery implements QueryInterface
{
public function __construct(
public readonly int $userId
) {}
}

Créer un Handler​

app/QueryHandlers/GetUserByIdQueryHandler.php
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​

app/Configurations/ApplicationConfiguration.php
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​

app/Controllers/UserController.php
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​

app/CommandHandlers/CreateUserCommandHandler.php
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​

app/QueryHandlers/GetUserByIdQueryHandler.php
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()​

app/Configurations/ApplicationConfiguration.php
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​

routes/app.php
// 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​

InterfaceMéthodeDescription
CommandInterface-Marqueur pour les commands
CommandHandlerInterfaceprocess(CommandInterface): mixedTraite une command
QueryInterface-Marqueur pour les queries
QueryHandlerInterfaceprocess(QueryInterface): mixedTraite une query

Bus​

ClasseMéthodeDescription
CommandBusexecute(CommandInterface): mixedExécute une command
QueryBusexecute(QueryInterface): mixedExé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.