CQRS - Command Query Responsibility Segregation
Introductionβ
CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates read operations (Queries) from write operations (Commands).
| Type | Description | Example |
|---|---|---|
| Command | Modifies the state of the system | Create, update, delete |
| Query | Reads data without modifying it | List, display, search |
When should you use CQRS?
CQRS is useful for complex systems where read and write needs differ. For simple applications, this pattern may add unnecessary complexity.
Installationβ
composer require bowphp/cqrs
Commandsβ
Commands represent intentions to modify the state of the system.
Creating a 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
) {}
}
Creating a 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),
]);
}
}
Registering the 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,
]);
}
}
Executing the 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β
Queries retrieve data without modifying the state of the system.
Creating a Queryβ
app/Queries/GetUserByIdQuery.php
namespace App\Queries;
use Bow\CQRS\Query\QueryInterface;
class GetUserByIdQuery implements QueryInterface
{
public function __construct(
public readonly int $userId
) {}
}
Creating a 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::retrieve($query->userId);
}
}
Registering the 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,
]);
}
Executing the 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'));
}
}
PHP Attributesβ
Use PHP 8 attributes to automatically bind 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
{
// Creation logic
}
}
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
{
// Retrieval logic
}
}
Registration with 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');
Recommended Structureβ
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 | Method | Description |
|---|---|---|
CommandInterface | - | Marker for commands |
CommandHandlerInterface | process(CommandInterface): mixed | Handles a command |
QueryInterface | - | Marker for queries |
QueryHandlerInterface | process(QueryInterface): mixed | Handles a query |
Busβ
| Class | Method | Description |
|---|---|---|
CommandBus | execute(CommandInterface): mixed | Executes a command |
QueryBus | execute(QueryInterface): mixed | Executes a query |
Is something missing?
If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.