Senior
What is CQRS and how can this model be applied in development?
sobes.tech AI
Answer from AI
CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates read operations (Queries) from write operations (Commands) on data. This model can be applied for:
- Responsibility separation: Separate models or services handle read requests (without changing state) and commands to change state (transactional operations).
- Scaling: Optimizing storage and handlers separately for read-heavy and write-heavy operations. For example, different databases with different schemas.
- Performance optimization: Read queries can use simple SQL or NoSQL for fast responses, while commands require more complex transactions and logic.
- Using Event Sourcing: Often used in conjunction with Event Sourcing, where commands generate events that are then used to update the read model.
- Increased flexibility: Allows using different technologies for reading and writing data.
Example of application:
- Command (Write): The
CreateOrderCommandhandler takes data, performs validation, updates the domain state (e.g., creates an order and reserves goods), and generates domain events (OrderCreated). - Query (Read): The
OrderQueryServiceuses an optimized read storage (e.g., denormalized data representation) for quick retrieval of order lists or details of a specific order.
Structural elements:
- Commands: Objects representing user or system intentions to change state. For example,
CreateProductCommand,PlaceOrderCommand. - Command Handlers: Accept commands and execute business logic, changing system state.
- Queries: Objects representing requests for data. For example,
GetProductQuery,GetCustomerOrdersQuery. - Query Handlers: Execute read requests, returning data without changing state.
- Read Model: Optimized for reading data, often denormalized or using another DBMS.
- Write Model: The model used for processing commands and changing state (often a domain model).
<?php
// Example command
final class CreateProductCommand
{
public function __construct(
public string $name,
public float $price
) {}
}
// Example command handler
final class CreateProductCommandHandler
{
public function __construct(
private ProductRepository $productRepository // Repository for writing
) {}
public function __invoke(CreateProductCommand $command): void
{
// Business logic, validation, and product creation
$product = new Product($command->name, $command->price);
$this->productRepository->save($product);
// Possibly generate a ProductCreated event
}
}
// Example query
final class GetProductQuery
{
public function __construct(
public string $productId
) {}
}
// Example query handler
final class GetProductQueryHandler
{
public function __construct(
private ProductReadRepository $productReadRepository // Repository for reading
) {}
public function __invoke(GetProductQuery $query): ?ReadProductDto // DTO for reading
{
// Query to optimized read storage
return $this->productReadRepository->findById($query->productId);
}
}
Applying CQRS requires additional complexity in development but can significantly improve system performance, scalability, and flexibility in complex scenarios.