---
name: cqrs-mediatr
description: CQRS pattern với MediatR. Use for Commands, Queries, Handlers, Pipeline Behaviors, và Idempotency.
compatibility: ".NET 10+, MediatR 12+, FluentValidation"
metadata:
  author: Velik Ho
  version: "1.0"
---

# CQRS & MediatR Patterns / Mẫu CQRS & MediatR

CQRS pattern với MediatR cho GoodGo microservices.

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Separating read/write operations / Tách biệt operations đọc/ghi
- Creating Commands and Queries / Tạo Commands và Queries
- Implementing MediatR handlers / Triển khai MediatR handlers
- Adding cross-cutting concerns via Behaviors / Thêm behaviors xuyên suốt
- Ensuring idempotency / Đảm bảo tính idempotent

## Core Concepts / Khái Niệm Cốt Lõi

### CQRS Overview / Tổng Quan CQRS

```
┌─────────────────────────────────────────────────────────────┐
│                        API Controller                        │
└─────────────────────┬───────────────────────────┬───────────┘
                      │                           │
              ┌───────▼───────┐           ┌───────▼───────┐
              │   Commands    │           │    Queries    │
              │ (Write/Ghi)   │           │  (Read/Đọc)   │
              └───────┬───────┘           └───────┬───────┘
                      │                           │
              ┌───────▼───────┐           ┌───────▼───────┐
              │   MediatR     │           │   MediatR     │
              │   Handler     │           │   Handler     │
              └───────┬───────┘           └───────┬───────┘
                      │                           │
              ┌───────▼───────┐           ┌───────▼───────┐
              │ Domain Model  │           │    Dapper     │
              │   EF Core     │           │   Raw SQL     │
              └───────┬───────┘           └───────┬───────┘
                      │                           │
              └───────────────┬───────────────────┘
                              │
                     ┌────────▼────────┐
                     │    Database     │
                     └─────────────────┘
```

### Command vs Query / Lệnh vs Truy Vấn

| Aspect | Command | Query |
|--------|---------|-------|
| **Purpose** | Modify state | Read data |
| **Returns** | Result/void | Data/DTO |
| **Model** | Full Domain Model | Lightweight DTO |
| **ORM** | EF Core | Dapper/Raw SQL |
| **Validation** | Full business rules | Minimal |

### MediatR Flow / Luồng MediatR

```
Request → Pipeline Behaviors → Handler → Response
          ├── LoggingBehavior
          ├── ValidationBehavior
          └── TransactionBehavior
```

## Key Patterns / Mẫu Chính

### Command Definition / Định Nghĩa Command

```csharp
/// <summary>
/// EN: Command to create a new order.
/// VI: Command tạo order mới.
/// </summary>
public record CreateOrderCommand(
    string UserId,
    Address ShippingAddress,
    List<OrderItemDto> Items) : IRequest<OrderResult>;

/// <summary>
/// EN: Command result.
/// VI: Kết quả command.
/// </summary>
public record OrderResult(Guid OrderId);
```

### Query Definition / Định Nghĩa Query

```csharp
/// <summary>
/// EN: Query to get user orders.
/// VI: Query lấy orders của user.
/// </summary>
public record GetUserOrdersQuery(
    string UserId,
    int Skip = 0,
    int Take = 20) : IRequest<PagedResult<OrderSummaryDto>>;

/// <summary>
/// EN: Lightweight DTO for query results.
/// VI: DTO nhẹ cho kết quả query.
/// </summary>
public record OrderSummaryDto(
    Guid Id,
    string Status,
    decimal TotalAmount,
    DateTime CreatedAt,
    int ItemCount);
```

### Command Handler / Handler Command

```csharp
/// <summary>
/// EN: Handler for CreateOrderCommand.
/// VI: Handler cho CreateOrderCommand.
/// </summary>
public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<CreateOrderCommandHandler> _logger;

    public CreateOrderCommandHandler(
        IOrderRepository orderRepository,
        ILogger<CreateOrderCommandHandler> logger)
    {
        _orderRepository = orderRepository;
        _logger = logger;
    }

    public async Task<OrderResult> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        // EN: Create order through domain model
        // VI: Tạo order qua domain model
        var order = new Order(request.UserId, request.ShippingAddress);

        foreach (var item in request.Items)
        {
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        }

        await _orderRepository.AddAsync(order, ct);
        await _orderRepository.UnitOfWork.SaveChangesAsync(ct);

        _logger.LogInformation(
            "EN: Order created / VI: Order đã tạo: {OrderId}", 
            order.Id);

        return new OrderResult(order.Id);
    }
}
```

### Query Handler with Dapper / Handler Query với Dapper

```csharp
/// <summary>
/// EN: Query handler using Dapper for optimized reads.
/// VI: Query handler dùng Dapper cho đọc tối ưu.
/// </summary>
public class GetUserOrdersQueryHandler 
    : IRequestHandler<GetUserOrdersQuery, PagedResult<OrderSummaryDto>>
{
    private readonly IDbConnection _connection;

    public GetUserOrdersQueryHandler(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<PagedResult<OrderSummaryDto>> Handle(
        GetUserOrdersQuery request,
        CancellationToken ct)
    {
        const string countSql = "SELECT COUNT(*) FROM Orders WHERE UserId = @UserId";
        var total = await _connection.ExecuteScalarAsync<int>(countSql, new { request.UserId });

        const string sql = @"
            SELECT o.Id, o.Status, o.TotalAmount, o.CreatedAt,
                   COUNT(oi.Id) as ItemCount
            FROM Orders o
            LEFT JOIN OrderItems oi ON o.Id = oi.OrderId
            WHERE o.UserId = @UserId
            GROUP BY o.Id, o.Status, o.TotalAmount, o.CreatedAt
            ORDER BY o.CreatedAt DESC
            OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY";

        var orders = await _connection.QueryAsync<OrderSummaryDto>(sql, new
        {
            request.UserId,
            request.Skip,
            request.Take
        });

        return new PagedResult<OrderSummaryDto>(orders.ToList(), total);
    }
}
```

### Validation Behavior / Behavior Validation

```csharp
/// <summary>
/// EN: Pipeline behavior for FluentValidation.
/// VI: Pipeline behavior cho FluentValidation.
/// </summary>
public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        if (!_validators.Any())
            return await next();

        var context = new ValidationContext<TRequest>(request);
        var failures = (await Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, ct))))
            .SelectMany(r => r.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count != 0)
            throw new ValidationException(failures);

        return await next();
    }
}
```

### Logging Behavior / Behavior Logging

```csharp
/// <summary>
/// EN: Pipeline behavior for logging.
/// VI: Pipeline behavior cho logging.
/// </summary>
public class LoggingBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        var requestName = typeof(TRequest).Name;

        _logger.LogInformation(
            "EN: Handling {RequestName} / VI: Xử lý {RequestName}",
            requestName);

        var stopwatch = Stopwatch.StartNew();
        var response = await next();
        stopwatch.Stop();

        _logger.LogInformation(
            "EN: Handled {RequestName} in {ElapsedMs}ms / VI: Đã xử lý {RequestName} trong {ElapsedMs}ms",
            requestName, stopwatch.ElapsedMilliseconds);

        return response;
    }
}
```

### Controller with MediatR / Controller với MediatR

```csharp
/// <summary>
/// EN: Slim controller using MediatR.
/// VI: Controller gọn nhẹ với MediatR.
/// </summary>
[ApiController]
[Route("api/v{version:apiVersion}/orders")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;

    public OrdersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<ActionResult<ApiResponse<OrderResult>>> CreateOrder(
        CreateOrderRequest request,
        CancellationToken ct)
    {
        var command = new CreateOrderCommand(
            GetUserId(),
            request.ShippingAddress,
            request.Items);

        var result = await _mediator.Send(command, ct);

        return CreatedAtAction(
            nameof(GetOrder),
            new { orderId = result.OrderId },
            ApiResponse<OrderResult>.Ok(result));
    }

    [HttpGet]
    public async Task<ActionResult<ApiResponse<PagedResult<OrderSummaryDto>>>> GetOrders(
        [FromQuery] int skip = 0,
        [FromQuery] int take = 20,
        CancellationToken ct = default)
    {
        var query = new GetUserOrdersQuery(GetUserId(), skip, take);
        var result = await _mediator.Send(query, ct);
        return Ok(ApiResponse<PagedResult<OrderSummaryDto>>.Ok(result));
    }

    private string GetUserId() =>
        User.FindFirstValue(ClaimTypes.NameIdentifier) ?? throw new UnauthorizedAccessException();
}
```

## Common Mistakes / Lỗi Thường Gặp

### 1. Using Domain Model for Queries

```csharp
// ❌ BAD: Using EF Core for simple reads
public async Task<IEnumerable<Order>> Handle(GetOrdersQuery query, CancellationToken ct)
{
    return await _context.Orders
        .Include(o => o.OrderItems)
        .Where(o => o.UserId == query.UserId)
        .ToListAsync(ct);
}

// ✅ GOOD: Using Dapper for optimized reads
public async Task<IEnumerable<OrderDto>> Handle(GetOrdersQuery query, CancellationToken ct)
{
    const string sql = "SELECT Id, Status, TotalAmount FROM Orders WHERE UserId = @UserId";
    return await _connection.QueryAsync<OrderDto>(sql, new { query.UserId });
}
```

### 2. Missing Pipeline Behaviors

```csharp
// ❌ BAD: Validation in handler
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
{
    if (string.IsNullOrEmpty(request.UserId))
        throw new ValidationException("UserId required");
    // ...
}

// ✅ GOOD: Validation via Behavior
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});
```

### 3. Fat Controllers

```csharp
// ❌ BAD: Logic in controller
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    var order = new Order(request.UserId, request.Address);
    foreach (var item in request.Items)
        order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
    await _repository.AddAsync(order);
    await _repository.UnitOfWork.SaveChangesAsync();
    return Ok(order.Id);
}

// ✅ GOOD: Delegate to MediatR
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    var command = request.ToCommand(GetUserId());
    var result = await _mediator.Send(command);
    return CreatedAtAction(nameof(GetOrder), new { orderId = result.OrderId }, result);
}
```

## Quick Reference / Tham Chiếu Nhanh

### MediatR Request Types

| Interface | Purpose | Example |
|-----------|---------|---------|
| `IRequest<T>` | Request with response | Commands, Queries |
| `IRequest` | Request without response | Fire-and-forget |
| `INotification` | Event notification | Domain events |

### Pipeline Behavior Order

```csharp
// EN: Registration order = execution order
// VI: Thứ tự đăng ký = thứ tự thực thi
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));      // 1st
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));   // 2nd
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));  // 3rd
```

### DI Registration

```csharp
// EN: Register MediatR with behaviors
// VI: Đăng ký MediatR với behaviors
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});

// EN: Register FluentValidation
// VI: Đăng ký FluentValidation
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
```

## Resources / Tài Nguyên

- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [Repository Pattern](../repository-pattern/SKILL.md) - Data access
- [Error Handling](../error-handling-patterns/SKILL.md) - Validation errors
- [Testing Patterns](../testing-patterns/SKILL.md) - Handler testing
- [API Design](../api-design/SKILL.md) - Controller patterns
