cqrs-architecture

CQRS Architecture Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cqrs-architecture" with this command: npx skills add melodic-software/claude-code-plugins/melodic-software-claude-code-plugins-cqrs-architecture

CQRS Architecture Skill

Design and implement Command Query Responsibility Segregation patterns for scalable systems.

MANDATORY: Documentation-First Approach

Before implementing CQRS:

  • Invoke docs-management skill for CQRS patterns

  • Verify patterns via MCP servers (perplexity, context7)

  • Base guidance on established CQRS literature

CQRS Fundamentals

Traditional vs CQRS:

TRADITIONAL (Single Model): ┌─────────────────────────────────┐ │ Application │ ├─────────────────────────────────┤ │ Domain Model │ │ (Reads + Writes) │ ├─────────────────────────────────┤ │ Database │ └─────────────────────────────────┘

CQRS (Separated Models): ┌───────────────┐ ┌───────────────┐ │ Command Side │ │ Query Side │ │ (Write Model) │ │ (Read Model) │ ├───────────────┤ ├───────────────┤ │ Domain Logic │ │ DTO/Views │ │ Aggregates │ │ Projections │ ├───────────────┤ ├───────────────┤ │ Write DB │───►│ Read DB │ └───────────────┘ └───────────────┘

CQRS Levels

Level 1: Logical Separation

Same database, separate code paths:

┌─────────────────────────────────────┐ │ Application │ ├──────────────────┬──────────────────┤ │ Command Handlers │ Query Handlers │ │ - Validation │ - Direct SQL │ │ - Domain Logic │ - Projections │ │ - Events │ - DTOs │ ├──────────────────┴──────────────────┤ │ Single Database │ └─────────────────────────────────────┘

Benefits: ✓ Clean separation in code ✓ Simple deployment ✓ Single source of truth ✓ Good starting point

Level 2: Separate Read Models

Same write DB, separate read DB:

┌─────────────────┐ ┌─────────────────┐ │ Command Side │ │ Query Side │ ├─────────────────┤ ├─────────────────┤ │ Command Handler │ │ Query Handler │ │ Domain Model │ │ DTOs │ ├─────────────────┤ ├─────────────────┤ │ Write Database │───►│ Read Database │ │ (Normalized) │sync│ (Denormalized) │ └─────────────────┘ └─────────────────┘

Benefits: ✓ Optimized read performance ✓ Scale reads independently ✓ Different storage technologies ✓ Eventually consistent reads

Level 3: Event-Sourced CQRS

Event store as write model, projections as read:

┌─────────────────┐ ┌─────────────────┐ │ Command Side │ │ Query Side │ ├─────────────────┤ ├─────────────────┤ │ Command Handler │ │ Query Handler │ │ Aggregate │ │ Read Models │ ├─────────────────┤ ├─────────────────┤ │ Event Store │───►│ Multiple Read │ │ (Append-only) │ │ Databases │ └─────────────────┘ └─────────────────┘

Benefits: ✓ Complete audit trail ✓ Temporal queries ✓ Multiple projections ✓ Rebuild read models

Command Side Design

Command Structure

// Command Definition public record PlaceOrderCommand( Guid CustomerId, List<OrderItemDto> Items, string ShippingAddress ) : ICommand<OrderId>;

// Command Handler public class PlaceOrderHandler : ICommandHandler<PlaceOrderCommand, OrderId> { private readonly IOrderRepository _repository; private readonly IEventPublisher _events;

public async Task&#x3C;OrderId> HandleAsync(
    PlaceOrderCommand command,
    CancellationToken ct)
{
    // Validation
    if (!command.Items.Any())
        throw new ValidationException("Order must have items");

    // Domain logic
    var order = Order.Create(
        command.CustomerId,
        command.Items.Select(i => new OrderItem(i.ProductId, i.Quantity)));

    // Persistence
    await _repository.SaveAsync(order, ct);

    // Publish events
    await _events.PublishAsync(order.GetDomainEvents(), ct);

    return order.Id;
}

}

Command Patterns

Command Best Practices:

NAMING:

  • Imperative: PlaceOrder, CancelOrder, UpdateAddress
  • Include context: not just "Create" but "CreateOrder"

STRUCTURE:

  • Immutable (records)
  • Only data needed for operation
  • No business logic in command

VALIDATION:

  • Input validation in handler
  • Business validation in domain
  • Return meaningful errors

IDEMPOTENCY:

  • Include idempotency key
  • Handle duplicate submissions
  • Return same result for retries

Query Side Design

Query Structure

// Query Definition public record GetOrderByIdQuery(Guid OrderId) : IQuery<OrderDetailsDto>;

// Query Handler public class GetOrderByIdHandler : IQueryHandler<GetOrderByIdQuery, OrderDetailsDto> { private readonly IReadDbContext _db;

public async Task&#x3C;OrderDetailsDto> HandleAsync(
    GetOrderByIdQuery query,
    CancellationToken ct)
{
    var order = await _db.OrderDetails
        .Where(o => o.OrderId == query.OrderId)
        .Select(o => new OrderDetailsDto
        {
            OrderId = o.OrderId,
            CustomerName = o.Customer.Name,
            Items = o.Items.Select(i => new OrderItemDto
            {
                ProductName = i.ProductName,
                Quantity = i.Quantity,
                Price = i.Price
            }).ToList(),
            Status = o.Status,
            TotalAmount = o.TotalAmount
        })
        .FirstOrDefaultAsync(ct);

    return order ?? throw new NotFoundException("Order not found");
}

}

Read Model Optimization

Query Optimization Strategies:

  1. DENORMALIZATION

    • Pre-join data
    • Store calculated values
    • Flatten hierarchies
  2. MATERIALIZED VIEWS

    • Database-managed
    • Automatically updated
    • Query-optimized
  3. CACHING

    • In-memory for hot data
    • Distributed for shared
    • Invalidate on events
  4. SPECIALIZED STORES

    • ElasticSearch for search
    • Redis for real-time
    • ClickHouse for analytics

Synchronization Patterns

Projection from Events

// Event-Driven Projection public class OrderProjection : IEventHandler<OrderPlaced>, IEventHandler<OrderShipped> { private readonly IOrderViewRepository _views;

public async Task HandleAsync(OrderPlaced @event, CancellationToken ct)
{
    var view = new OrderView
    {
        OrderId = @event.OrderId,
        CustomerId = @event.CustomerId,
        Status = "Placed",
        PlacedAt = @event.Timestamp,
        ItemCount = @event.Items.Count,
        TotalAmount = @event.TotalAmount
    };

    await _views.InsertAsync(view, ct);
}

public async Task HandleAsync(OrderShipped @event, CancellationToken ct)
{
    await _views.UpdateAsync(@event.OrderId, view =>
    {
        view.Status = "Shipped";
        view.ShippedAt = @event.Timestamp;
        view.TrackingNumber = @event.TrackingNumber;
    }, ct);
}

}

Consistency Patterns

Consistency Options:

STRONG CONSISTENCY (Same Transaction): ┌──────────┐ ┌──────────┐ │ Command │───►│ Read │ │ DB │ │ Model │ │ │ │ Update │ └──────────┴────┴──────────┘ Same Transaction

EVENTUAL CONSISTENCY (Async): ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Command │───►│ Message │───►│ Read │ │ DB │ │ Queue │ │ Model │ └──────────┘ └──────────┘ └──────────┘ Async, Eventually Consistent

HYBRID (Read-Your-Writes):

  • Immediate read from command side
  • Eventually consistent for others
  • Version checking in queries

MediatR Implementation

Setup with MediatR

// Registration services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); });

// Command/Query Interfaces public interface ICommand<TResult> : IRequest<TResult> { } public interface IQuery<TResult> : IRequest<TResult> { }

// Handler Interfaces public interface ICommandHandler<TCommand, TResult> : IRequestHandler<TCommand, TResult> where TCommand : ICommand<TResult> { }

public interface IQueryHandler<TQuery, TResult> : IRequestHandler<TQuery, TResult> where TQuery : IQuery<TResult> { }

Pipeline Behaviors

// Validation Behavior public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly IEnumerable<IValidator<TRequest>> _validators;

public async Task&#x3C;TResponse> Handle(
    TRequest request,
    RequestHandlerDelegate&#x3C;TResponse> next,
    CancellationToken ct)
{
    var failures = _validators
        .Select(v => v.Validate(request))
        .SelectMany(r => r.Errors)
        .Where(f => f != null)
        .ToList();

    if (failures.Any())
        throw new ValidationException(failures);

    return await next();
}

}

// Logging Behavior public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull { private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

public async Task&#x3C;TResponse> Handle(
    TRequest request,
    RequestHandlerDelegate&#x3C;TResponse> next,
    CancellationToken ct)
{
    _logger.LogInformation("Handling {RequestType}", typeof(TRequest).Name);
    var response = await next();
    _logger.LogInformation("Handled {RequestType}", typeof(TRequest).Name);
    return response;
}

}

API Design with CQRS

REST API Pattern

[ApiController] [Route("api/orders")] public class OrdersController : ControllerBase { private readonly IMediator _mediator;

// Commands use POST/PUT/DELETE
[HttpPost]
public async Task&#x3C;ActionResult&#x3C;OrderId>> PlaceOrder(
    [FromBody] PlaceOrderCommand command,
    CancellationToken ct)
{
    var orderId = await _mediator.Send(command, ct);
    return CreatedAtAction(nameof(GetOrder), new { id = orderId }, orderId);
}

// Queries use GET
[HttpGet("{id}")]
public async Task&#x3C;ActionResult&#x3C;OrderDetailsDto>> GetOrder(
    Guid id,
    CancellationToken ct)
{
    var order = await _mediator.Send(new GetOrderByIdQuery(id), ct);
    return Ok(order);
}

[HttpGet]
public async Task&#x3C;ActionResult&#x3C;PagedResult&#x3C;OrderSummaryDto>>> ListOrders(
    [FromQuery] ListOrdersQuery query,
    CancellationToken ct)
{
    var orders = await _mediator.Send(query, ct);
    return Ok(orders);
}

}

When to Use CQRS

Good Fit

CQRS Works Well For:

✓ Complex reads AND writes

  • Different optimization needs
  • Read/write ratio imbalance

✓ Multiple views of data

  • Different query patterns
  • Multiple UI requirements

✓ Collaborative domains

  • Many concurrent users
  • Complex validation

✓ Event-driven systems

  • Microservices
  • Async processing

✓ Scalability requirements

  • Independent read/write scaling
  • Performance optimization

Poor Fit

CQRS May Not Fit:

✗ Simple CRUD applications

  • Overhead not justified
  • Same model works fine

✗ Small team/project

  • Added complexity
  • Maintenance burden

✗ Strong consistency required

  • Real-time requirements
  • Financial transactions

✗ Unknown query patterns

  • Ad-hoc reporting
  • BI requirements

Workflow

When implementing CQRS:

  • Evaluate Fit: Is CQRS appropriate for this context?

  • Choose Level: Logical, physical, or event-sourced?

  • Design Commands: Identify write operations

  • Design Queries: Identify read patterns

  • Plan Sync: How will read models be updated?

  • Implement Pipeline: Validation, logging, etc.

  • Consider Consistency: What guarantees are needed?

  • Test Both Sides: Command and query testing

References

For detailed guidance:

Last Updated: 2025-12-26

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

design-thinking

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

plantuml-syntax

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

system-prompt-engineering

No summary provided by upstream source.

Repository SourceNeeds Review