CQS and CQRS Made Easy in C#: Learn with Hands-On Examples

Command Query Separation (CQS) and Command Query Responsibility Segregation (CQRS) are two architectural patterns that play a crucial role in designing scalable and maintainable software systems. These patterns are often employed in combination, leveraging the strengths of each to achieve better code organization and performance. In this article, we will delve into CQS and CQRS, providing simple explanations and practical examples using C#.

Command Query Separation (CQS)

CQS is a principle that suggests separating the responsibilities of commands (actions that change the system state) from queries (actions that retrieve information without altering the system state). The main idea is to have methods either perform an action (command) or return data (query), but not both.

CQS and CQRS

Let’s consider a simple scenario of managing a shopping cart. In a CQS-compliant approach, we might have separate methods for adding an item to the cart (command) and retrieving the cart contents (query).

public class ShoppingCart
{
    private List<string> items = new List<string>();

    // Command: Add item to the cart
    public void AddItem(string item)
    {
        items.Add(item);
    }

    // Query: Retrieve cart contents
    public List<string> GetCartContents()
    {
        return items;
    }
}

In this example, the AddItem method modifies the state of the ShoppingCart by adding an item, while the GetCartContents method only retrieves the existing items without altering the cart.

Command Query Responsibility Segregation (CQRS)

CQRS takes the concept of CQS a step further by segregating the responsibility for handling commands and queries into distinct components. In a CQRS architecture, there are separate models for reading data (queries) and updating data (commands).

Building upon the shopping cart example, a CQRS approach would involve creating separate classes for handling commands and queries.

// Command Model
public class ShoppingCartCommandHandler
{
    private List<string> items = new List<string>();

    public void HandleAddItemCommand(string item)
    {
        items.Add(item);
    }
}

// Query Model
public class ShoppingCartQueryHandler
{
    private List<string> items = new List<string>();

    public List<string> HandleGetCartContentsQuery()
    {
        return items;
    }
}

In this CQRS example, the ShoppingCartCommandHandler handles commands (e.g., adding items), and the ShoppingCartQueryHandler deals with queries (e.g., retrieving cart contents). This separation allows for specialized optimization strategies for reading and writing, enhancing overall system performance.

Combining CQS and CQRS in C#

In practical scenarios, CQS and CQRS are often used together to strike a balance between simplicity and scalability. Let’s expand our shopping cart example to illustrate the combined use of these patterns.

// Command Model (for updates)
public class ShoppingCartCommandHandler
{
    private List<string> items = new List<string>();

    public void HandleAddItemCommand(string item)
    {
        items.Add(item);
    }
}

// Query Model (for reads)
public class ShoppingCartQueryHandler
{
    private List<string> items = new List<string>();

    public List<string> HandleGetCartContentsQuery()
    {
        return items;
    }
}

// Combined Model
public class ShoppingCartService
{
    private readonly ShoppingCartCommandHandler commandHandler;
    private readonly ShoppingCartQueryHandler queryHandler;

    public ShoppingCartService(ShoppingCartCommandHandler commandHandler, ShoppingCartQueryHandler queryHandler)
    {
        this.commandHandler = commandHandler;
        this.queryHandler = queryHandler;
    }

    // Command: Add item to the cart
    public void AddItem(string item)
    {
        commandHandler.HandleAddItemCommand(item);
    }

    // Query: Retrieve cart contents
    public List<string> GetCartContents()
    {
        return queryHandler.HandleGetCartContentsQuery();
    }
}

Here, the ShoppingCartService acts as a mediator, using the ShoppingCartCommandHandler for commands and the ShoppingCartQueryHandler for queries. This separation of concerns enhances code maintainability, scalability, and the ability to optimize read and write operations independently.

Conclusion:

In conclusion, CQS and CQRS are architectural patterns that promote code clarity, maintainability, and scalability by separating command (write) and query (read) responsibilities. In C#, these patterns can be implemented individually or in combination, depending on the specific requirements of the application. Understanding and applying these patterns can contribute significantly to the development of robust and efficient software systems.

Scroll to Top