CQRS Pattern
debt(d7/e7/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). CQRS is an architectural pattern — there are no static analysis tools that can detect whether you've over-applied CQRS, returned data from command handlers inappropriately, or prematurely adopted full event sourcing. These issues only surface during code review, architectural discussions, or when performance/complexity problems manifest at runtime. No detection_hints.tools are specified for this term.
Closest to 'cross-cutting refactor across the codebase' (e7). While the quick_fix suggests starting simple with separate ReadRepository/WriteRepository classes, fixing CQRS misapplication (such as having applied it everywhere, or having committed to full event sourcing prematurely) requires significant architectural rework. Untangling command handlers, query handlers, event stores, and read model projections across the codebase is a major undertaking that touches many components and requires careful migration.
Closest to 'strong gravitational pull' (b7). CQRS fundamentally shapes how code is organised — every feature must be split into commands and queries, handlers must be created, and the team must maintain discipline around the separation. The applies_to shows it affects both web and cli contexts. Once adopted, every new feature is shaped by this pattern. The common_mistakes note that applying CQRS everywhere adds complexity, and the misconception warns that full CQRS+ES defines the system's data architecture.
Closest to 'serious trap' (t7). The misconception field explicitly states developers believe 'CQRS requires event sourcing and a message bus' when in reality it exists on a spectrum starting with simple class separation. This contradicts how the pattern is commonly presented in tutorials and conference talks. The common_mistakes reinforce traps: returning data from commands (seems natural, is wrong), synchronous consistency when eventual is intended, and jumping to full event sourcing prematurely. These are not edge cases but fundamental misunderstandings.
Also Known As
TL;DR
Explanation
CQRS separates an application into two distinct sides: the command side handles write operations (create, update, delete) through command objects processed by command handlers, updating a normalised write model; the query side handles read operations through query objects processed by query handlers, reading from a denormalised read model optimised for display. The read model can be a database view, a cached projection, a separate database, or even a different storage technology than the write model. In its simplest form, CQRS in PHP is just a coding pattern — separate service classes for reads and writes, no shared models. In advanced implementations, the read model is derived from events emitted by the write model (event sourcing), enabling separate scaling, separate databases, and rebuilding the read model from scratch by replaying events. Laravel has several CQRS libraries (Tactician, Broadway); Symfony provides the Messenger component which fits the command bus pattern.
Common Misconception
Why It Matters
Common Mistakes
- Applying CQRS to every part of the application — it adds complexity and is only justified where read/write models genuinely diverge or where separate scaling is needed.
- Returning data from command handlers — commands should return void or a command ID; read the updated state with a query if needed.
- Keeping the read and write models in sync synchronously when they are meant to be eventually consistent — this negates the performance benefit of separate models.
- Starting with the full event sourcing implementation before validating that the simpler CQRS separation solves the problem — YAGNI applies.
Avoid When
- Simple CRUD applications with straightforward read/write patterns where the added complexity of separate models provides no performance or scalability benefit.
- Teams unfamiliar with event-driven architecture or asynchronous messaging, as CQRS introduces operational complexity that requires solid infrastructure and debugging practices.
- Systems where read and write models must stay perfectly consistent in real-time, since eventual consistency between separated models is often difficult to eliminate.
- Projects with tight deadlines or limited budget, as the initial overhead of implementing dual models typically delays feature delivery compared to a unified model approach.
When To Use
- Your read and write loads are asymmetrical — queries vastly outnumber writes or require different optimization strategies (caching, denormalisation, separate scaling).
- You need an audit trail or event history of state changes — event sourcing with CQRS gives you a complete record and the ability to rebuild read models.
- Your read model requires a shape different from your write model — for example, writes normalise data into relational tables but reads need a flat, denormalised view for performance.
- You're building a system where multiple services or teams need to consume the same data in different shapes — CQRS lets each service maintain its own optimised read projection.
Code Examples
// Mixed read/write in one service — tight coupling
class OrderService {
public function createOrder(array $data): Order {
$order = Order::create($data);
// Read mixed with write
return Order::with(['items', 'user', 'address'])
->find($order->id); // N+1, heavy join for a write operation
}
}
// Separate command and query handlers
class CreateOrderHandler {
public function handle(CreateOrder $cmd): string {
$order = new Order($cmd->customerId, $cmd->items);
$this->orders->save($order);
$this->eventBus->publish(new OrderCreated($order->id));
return $order->id; // return ID only, not full read model
}
}
class OrderSummaryQuery {
public function findById(string $id): OrderSummaryDTO {
// Reads from denormalised read model — fast, no joins
return $this->readDb->query(
'SELECT * FROM order_summaries WHERE id = ?', [$id]
);
}
}