Course contentsShow
← C#
Lesson 1552 of 1,90814. Background Work and MessagingFree lesson

CQRS in Background Processing

Separate command and query models to optimize performance in event-driven systems.

In this lesson, you will learn how to decouple your background processing infrastructure by separating the concerns of command execution from data retrieval.

The Problem: Tight Coupling

In typical background processing, we often use the same database schema to both record the outcome of a processed message and to serve data to the user interface. As your background workers scale to handle high-throughput event streams, the constant contention between "Command" operations (writing results of background jobs) and "Query" operations (fetching those results for dashboards or status reports) creates a performance bottleneck.

Separating Concerns with CQRS

Command Query Responsibility Segregation (CQRS) allows us to optimize these two paths independently. In a background-heavy system:

  1. The Command Side: When a worker finishes processing a message, it performs a single, optimized write to an event store or a command-side database. It doesn't worry about how that data will be viewed; it only ensures the domain state is updated reliably.
  2. The Query Side: A separate projection service listens to these domain events. It transforms the raw state into a "read model"—a denormalized database schema designed specifically for fast lookups.

Example: Processing a Task

Imagine a worker processing a large document. Instead of updating a complex relational table, it emits an Event.

// Command Side: Focused on the action
public async Task Handle(ProcessDocumentCommand command) {
    var result = await _processor.ExecuteAsync(command);
    await _eventBus.PublishAsync(new DocumentProcessedEvent(result.Id, result.Status));
}

// Query Side: Focused on performance
public class DocumentStatusProjection {
    public void Handle(DocumentProcessedEvent @event) {
        // Update a read-only View model table specifically for UI queries
        _readDb.DocumentViews.Update(@event.Id, @event.Status);
    }
}

By splitting these, you can scale the Command side to handle massive concurrent writes (using techniques like the Outbox pattern we discussed earlier) while keeping the Query side responsive to user requests without hitting the transactional database. This architecture ensures that even when your background workers are under heavy load, your system’s visibility remains performant and decoupled.

Key Takeaway: Use CQRS in background processing to isolate high-write command workloads from read-heavy dashboard requests, enabling independent scaling and optimized data modeling for each responsibility.