CQRS with Example

CQRS:

CQRS (Command Query Responsibility Segregation) is a pattern that separates data manipulation (commands) from data retrieval (queries) in an application. This allows you to manage and scale read and write operations independently.

Commands perform operations that modify state (e.g., create, update, delete).

Queries retrieve data without changing state (e.g., fetching records).


The Mediator Pattern helps in managing the interaction between objects by acting as an intermediary. Instead of objects communicating with each other directly, they send messages (commands, queries, etc.) to a central mediator, which forwards these messages to the appropriate handler. This promotes loose coupling and improves maintainability and scalability.


The Mediator Pattern helps decouple components by sending messages through a central mediator instead of allowing direct communication between them. This pattern is ideal for implementing CQRS (Command Query Responsibility Segregation), where commands (write operations) and queries (read operations) are separated.


Benefits of CQRS:

Separation of Concerns: Keeps commands and queries distinct, making code easier to maintain.

Scalability: Allows independent scaling of read and write operations.

Optimized Models: Different data models for reads (often denormalized) and writes (normalized).

Better Testability: Isolated testing for commands and queries.

Complexity Handling: Useful for complex domains where separate handling for read and write operations simplifies business logic.


CQRS (Command Query Responsibility Segregation) is often used in scenarios where there are complex operations and high scalability requirements, particularly when dealing with bulk operations, concurrent access, or highly distributed systems.



Example in .NET Core:

Command (modifies data):

public class CreateUserCommand

{

    public string Name { get; set; }

    public string Email { get; set; }

}


Command Handler (executes the command):

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, bool>

{

    private readonly IUserRepository _userRepository;

    public CreateUserCommandHandler(IUserRepository userRepository) => _userRepository = userRepository;


    public async Task<bool> Handle(CreateUserCommand request, CancellationToken cancellationToken)

    {

        var user = new User { Name = request.Name, Email = request.Email };

        await _userRepository.AddAsync(user);

        return true;

    }

}


Query (retrieves data):

public class GetUserQuery

{

    public int UserId { get; set; }

}


Query Handler (executes the query):

public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDTO>

{

    private readonly IUserRepository _userRepository;

    public GetUserQueryHandler(IUserRepository userRepository) => _userRepository = userRepository;


    public async Task<UserDTO> Handle(GetUserQuery request, CancellationToken cancellationToken)

    {

        var user = await _userRepository.GetByIdAsync(request.UserId);

        return new UserDTO { Id = user.Id, Name = user.Name, Email = user.Email };

    }

}


Controller:

[ApiController]

[Route("api/users")]

public class UserController : ControllerBase

{

    private readonly IMediator _mediator;

    public UserController(IMediator mediator) => _mediator = mediator;


    [HttpGet("{userId}")]

    public async Task<IActionResult> GetUser(int userId)

    {

        var query = new GetUserQuery { UserId = userId };

        var user = await _mediator.Send(query);

        return Ok(user);

    }


    [HttpPost]

    public async Task<IActionResult> CreateUser([FromBody] CreateUserCommand command)

    {

        var result = await _mediator.Send(command);

        return Ok(result);

    }

}


When to Use CQRS:

High Read/Write Demand: Independent scaling of reads and writes.

Complex Business Logic: Separate handling of queries and commands simplifies logic.

Distributed Systems: Decouples components, helpful in microservices.


When to Avoid:

Simple CRUD Applications: Extra complexity for basic needs.

Tight Deadlines/Small Teams: More infrastructure and overhead for simpler apps.

This streamlined version highlights the core concepts of CQRS while omitting repetition and unnecessary details.



1. CQRS and Bulk Operations:

   - CQRS separates read (queries) and write (commands) operations, optimizing each independently.

   - For write operations, commands manage data changes (e.g., creating, updating), often involving complex validation or bulk data.

   - For read operations, queries are optimized for performance (e.g., using denormalized views, caching).

   - CQRS allows independent scaling of reads (e.g., using read replicas) and writes (e.g., optimized for consistency), improving performance in bulk operations.


2. Concurrency and CQRS:

   - CQRS minimizes contention between reads and writes by separating their models.

   - Write side ensures transaction consistency, using mechanisms like event sourcing or distributed transactions.

   - Eventual consistency is often acceptable on the read side, allowing for high performance and asynchronous updates.

   - Read models can be denormalized to improve query performance and handle high concurrency.


3. CQRS in Highly Concurrent Scenarios:

   - In high-concurrency environments (e.g., e-commerce, online trading), read and write operations can interfere with each other.

   - Separating reads and writes allows:

     - Write side to use locks or optimistic concurrency for conflict-free operations.

     - Read side to use optimized, eventually consistent data models for performance.

   - This separation enhances handling of concurrency and eventual consistency.


4. When CQRS Helps:

   - High write or read load: CQRS allows independent scaling of reads and writes.

   - Complex business logic: CQRS enables better management of complex validation and business rules on the write side.

   - Asynchronous operations: CQRS supports asynchronous updates, allowing read models to be updated without blocking the write side.


5. Example Use Case: E-Commerce:

   - Order creation: Commands handle bulk order creation and stock updates, ensuring consistency.

   - Inventory queries: The query model can be optimized for performance, using techniques like caching and denormalization to handle bulk read operations efficiently.






Comments

Popular posts from this blog

Multiline to singleline IN C# - CODING

EF Core interview questions for beginners

EF Core interview questions for experienced