Receber por email
← Voltar para o catálogo

cqs-patterns

Command Query Separation (CQS) and CQRS patterns for .NET. Use when designing methods, handlers, and application architecture. Ensures predictable, testable code.

367estrelas
Atualizado há 4 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add aiskillstore/marketplace

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/cqs-patterns/svg)](https://www.skillteca.com.br/skills/cqs-patterns)

Skills relacionadas

Ver todas de Segurança →

Alerta por categoria

Receba novas skills de Segurança toda segunda

1 email curto, só com as skills novas de Segurança. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Command Query Separation (CQS)

The Principle

A method should either be a Command that performs an action, or a Query that returns data, but not both.
┌─────────────────────────────────────────────────────────┐
│                        METHOD                           │
├───────────────────────┬─────────────────────────────────┤
│       COMMAND         │            QUERY                │
├───────────────────────┼─────────────────────────────────┤
│ - Changes state       │ - Returns data                  │
│ - Returns void        │ - No side effects               │
│ - "Do something"      │ - "Tell me something"           │
│ - Imperative verbs    │ - Noun or question              │
│   (Create, Update,    │   (Get, Find, Is, Has,          │
│    Delete, Process)   │    Calculate, Count)            │
└───────────────────────┴─────────────────────────────────┘

CQS at Method Level

Violation Examples

// BAD: Method both modifies state AND returns data
public class Stack<T>
{
    public T Pop()  // Both removes item AND returns it
    {
        var item = _items[_count - 1];
        _count--;
        return item;
    }
}

// BAD: Getter with side effects
public class Counter
{
    private int _value;

    public int Value
    {
        get { return _value++; }  // Query that modifies state!
    }
}

// BAD: Command returns data
public class UserService
{
    public User CreateUser(string email, string name)  // Returns created user
    {
        var user = new User { Email = email, Name = name };
        _repository.Add(user);
        return user;  // CQS violation
    }
}

Correct CQS Implementation

// GOOD: Separated commands and queries
public class Stack<T>
{
    // Query - returns data, no side effects
    public T Peek()
    {
        if (_count == 0)
            throw new InvalidOperationException("Stack is empty");
        return _items[_count - 1];
    }

    // Command - modifies state, returns void
    public void Pop()
    {
        if (_count == 0)
            throw new InvalidOperationException("Stack is empty");
        _count--;
    }

    // Usage: separate calls
    var top = stack.Peek();
    stack.Pop();
}

// GOOD: Counter with pure query
public class Counter
{
    private int _value;

    public int Value => _value;  // Pure query

    public void Increment() => _value++;  // Command
}

// GOOD: User service with CQS
public class UserService
{
    // Command - creates user, returns identifier only
    public Guid CreateUser(string email, string name)
    {
        var userId = Guid.NewGuid();
        var user = new User { Id = userId, Email = email, Name = name };
        _repository.Add(user);
        return userId;  // Returning ID is acceptable
    }

    // Query - retrieves user
    public User? GetUser(Guid userId)
    {
        return _repository.GetById(userId);
    }
}

CQS Exceptions

Some situations justify combining command and query:

1. Atomic Operations

// Acceptable: Interlocked operations need to be atomic
public int IncrementAndGet()
{
    return Interlocked.Increment(ref _counter);
}

// Acceptable: Compare-and-swap patterns
public bool TryUpdate(int expected, int newValue)
{
    return Interlocked.CompareExchange(ref _value, newValue, expected) == expected;
}

2. Fluent APIs

// Acceptable: Builder pattern returns this
public class QueryBuilder
{
    public QueryBuilder Where(string condition)
    {
        _conditions.Add(condition);
        return this;  // Returns modified builder
    }

    public QueryBuilder OrderBy(string column)
    {
        _orderBy = column;
        return this;
    }
}

3. Factory Methods

// Acceptable: Creation returns the created object
public static Order Create(int customerId, IEnumerable<OrderItem> items)
{
    return new Order
    {
        Id = Guid.NewGuid(),
        CustomerId = customerId,
        Items = items.ToList()
    };
}

CQRS - Command Query Responsibility Segregation

CQRS applies CQS at the architectural level, using separate models for reads and writes.

                    ┌─────────────────────────────────────┐
                    │           APPLICATION               │
                    └─────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │   COMMANDS    │               │    QUERIES    │
           │    (Write)    │               │    (Read)     │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ Command       │               │ Query         │
           │ Handlers      │               │ Handlers      │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ Domain Model  │               │ Read Model    │
           │ (Rich)        │               │ (DTOs)        │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ Write DB      │──────────────▶│ Read DB       │
           │ (Normalized)  │  Sync/Events  │ (Optimized)   │
           └───────────────┘               └───────────────┘

Commands and Queries

// === MARKER INTERFACES ===

public interface ICommand { }

public interface ICommand<TResult> { }

public interface IQuery<TResult> { }

// === COMMANDS ===

public record CreateOrderCommand(
    int CustomerId,
    List<OrderItemDto> Items
) : ICommand<Guid>;

public record UpdateOrderStatusCommand(
    Guid OrderId,
    OrderStatus NewStatus
) : ICommand;

public record CancelOrderCommand(Guid OrderId) : ICommand;

// === QUERIES ===

public record GetOrderByIdQuery(Guid OrderId) : IQuery<OrderDto?>;

public record GetOrdersByCustomerQuery(
    int CustomerId,
    int Page = 1,
    int PageSize = 10
) : IQuery<PagedResult<OrderSummaryDto>>;

public record GetOrderStatisticsQuery(
    DateTime From,
    DateTime To
) : IQuery<OrderStatisticsDto>;

Command Handlers

// === HANDLER INTERFACES ===

public interface ICommandHandler<in TCommand> where TCommand : ICommand
{
    Task HandleAsync(TCommand command, CancellationToken ct = default);
}

public interface ICommandHandler<in TCommand, TResult> where TCommand : ICommand<TResult>
{
    Task<TResult> HandleAsync(TCommand command, CancellationToken ct = default);
}

// === IMPLEMENTATION ===

public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand, Guid>
{
    private readonly IOrderRepository _orderRepository;
    private readonly ICustomerRepository _customerRepository;
    private readonly IEventPublisher _eventPublisher;

    public CreateOrderCommandHandler(
        IOrderRepository orderRepository,
        ICustomerRepository customerRepository,
        IEventPublisher eventPublisher)
    {
        _orderRepository = orderRepository;
        _customerRepository = customerRepository;
        _eventPublisher = eventPublisher;
    }

    public async Task<Guid> HandleAsync(CreateOrderCommand command, CancellationToken ct)
    {
        // Validate
        var customer = await _customerRepository.GetByIdAsync(command.CustomerId, ct);
        if (customer == null)
            throw new CustomerNotFoundException(command.CustomerId);

        // Create domain entity
        var order = Order.Create(command.CustomerId);
        foreach (var item 
Compartilhar X LinkedIn