SYS://VISION.ACTIVE
VIEWPORT.01
LAT 28.0222° N
SIGNAL.NOMINAL
VISION Loading
Back to Blog

CQRS: Separating Reads from Writes for Better Performance

Shane Barron

Shane Barron

Laravel Developer & AI Integration Specialist

The CQRS Concept

CQRS separates read operations (queries) from write operations (commands). This allows you to optimize each path independently—denormalized read models for fast queries, normalized write models for data integrity.

Why Separate?

  • Read and write patterns often have different requirements
  • Read-heavy applications can scale reads independently
  • Complex queries can use optimized read models
  • Write models stay focused on business rules

Implementation

Commands

class PlaceOrderCommand
{
    public function __construct(
        public readonly string $customerId,
        public readonly array $items,
        public readonly string $shippingAddress
    ) {}
}

class PlaceOrderHandler
{
    public function handle(PlaceOrderCommand $command): void
    {
        $order = Order::create([
            'customer_id' => $command->customerId,
            'status' => 'pending',
        ]);

        foreach ($command->items as $item) {
            $order->items()->create($item);
        }

        event(new OrderPlaced($order));
    }
}

Queries

class GetOrderSummaryQuery
{
    public function __construct(
        public readonly string $orderId
    ) {}
}

class GetOrderSummaryHandler
{
    public function handle(GetOrderSummaryQuery $query): OrderSummaryDTO
    {
        // Read from optimized read model
        $summary = OrderSummary::find($query->orderId);

        return new OrderSummaryDTO(
            $summary->order_id,
            $summary->customer_name,
            $summary->total_formatted,
            $summary->status_label,
            $summary->items_count
        );
    }
}

Read Model Projections

class OrderSummaryProjector
{
    public function onOrderPlaced(OrderPlaced $event): void
    {
        $order = Order::with(['customer', 'items'])->find($event->orderId);

        OrderSummary::create([
            'order_id' => $order->id,
            'customer_name' => $order->customer->name,
            'total_formatted' => Number::currency($order->total),
            'status_label' => $order->status->label(),
            'items_count' => $order->items->count(),
        ]);
    }

    public function onOrderStatusChanged(OrderStatusChanged $event): void
    {
        OrderSummary::where('order_id', $event->orderId)
            ->update(['status_label' => $event->newStatus->label()]);
    }
}

Command Bus

class CommandBus
{
    public function dispatch(object $command): void
    {
        $handlerClass = $this->resolveHandler($command);
        $handler = app($handlerClass);
        $handler->handle($command);
    }

    private function resolveHandler(object $command): string
    {
        $commandClass = get_class($command);
        return str_replace('Command', 'Handler', $commandClass);
    }
}

When to Use CQRS

  • Read and write patterns differ significantly
  • Complex reporting requirements
  • High read-to-write ratio
  • Need to scale reads and writes independently

Conclusion

CQRS adds complexity but provides powerful optimization opportunities. Use it when read and write requirements diverge significantly, not as a default architecture.

Share this article
Shane Barron

Shane Barron

Strategic Technology Architect with 40 years of experience building production systems. Specializing in Laravel, AI integration, and enterprise architecture.

Need Help With Your Project?

I respond to all inquiries within 24 hours. Let's discuss how I can help build your production-ready system.

Get In Touch