Skip to main content
Version: CANARY 🚧

CQRS - Command Query Responsibility Segregation

Introduction​

CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates read operations (Queries) from write operations (Commands).

TypeDescriptionExample
CommandModifies the state of the systemCreate, update, delete
QueryReads data without modifying itList, 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');
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​

InterfaceMethodDescription
CommandInterface-Marker for commands
CommandHandlerInterfaceprocess(CommandInterface): mixedHandles a command
QueryInterface-Marker for queries
QueryHandlerInterfaceprocess(QueryInterface): mixedHandles a query

Bus​

ClassMethodDescription
CommandBusexecute(CommandInterface): mixedExecutes a command
QueryBusexecute(QueryInterface): mixedExecutes 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.