# Mẫu CQRS & MediatR

> **Skill**: `cqrs-mediatr`  
> **Compatibility**: .NET 10+, MediatR 12+, FluentValidation

## Tổng Quan

CQRS (Command Query Responsibility Segregation) tách biệt các operations đọc và ghi để cải thiện khả năng mở rộng và bảo trì. MediatR cung cấp cơ sở hạ tầng để triển khai các mẫu CQRS với pipeline behaviors.

## Khi Nào Sử Dụng

- Tách biệt operations đọc/ghi
- Tạo Commands và Queries
- Triển khai MediatR handlers
- Thêm cross-cutting concerns qua Pipeline Behaviors
- Đảm bảo tính idempotency

## Tổng Quan CQRS

```
┌──────────────────────────────────────┐
│          API Controller              │
└────────┬─────────────────┬───────────┘
         │                 │
   ┌─────▼─────┐     ┌─────▼─────┐
   │ Commands  │     │  Queries  │
   │ (Ghi)     │     │  (Đọc)    │
   └─────┬─────┘     └─────┬─────┘
         │                 │
   ┌─────▼─────┐     ┌─────▼─────┐
   │  Handler  │     │  Handler  │
   └─────┬─────┘     └─────┬─────┘
         │                 │
   ┌─────▼─────┐     ┌─────▼─────┐
   │  EF Core  │     │   Dapper  │
   └───────────┘     └───────────┘
```

| Khía Cạnh | Command | Query |
|-----------|---------|-------|
| **Mục đích** | Thay đổi trạng thái | Đọc dữ liệu |
| **Trả về** | Result/void | Data/DTO |
| **ORM** | EF Core | Dapper/Raw SQL |

## Bắt Đầu Nhanh

### 1. Định Nghĩa Command

```csharp
public record CreateOrderCommand(
    string UserId,
    Address ShippingAddress,
    List<OrderItemDto> Items) : IRequest<OrderResult>;

public record OrderResult(Guid OrderId);
```

### 2. Tạo Handler

```csharp
public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _repository;
    
    public async Task<OrderResult> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        var order = new Order(request.UserId, request.ShippingAddress);
        
        foreach (var item in request.Items)
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        
        await _repository.AddAsync(order, ct);
        await _repository.UnitOfWork.SaveChangesAsync(ct);
        
        return new OrderResult(order.Id);
    }
}
```

### 3. Query với Dapper

```csharp
public record GetUserOrdersQuery(
    string UserId,
    int Skip = 0,
    int Take = 20) : IRequest<PagedResult<OrderSummaryDto>>;

public class GetUserOrdersQueryHandler 
    : IRequestHandler<GetUserOrdersQuery, PagedResult<OrderSummaryDto>>
{
    private readonly IDbConnection _connection;
    
    public async Task<PagedResult<OrderSummaryDto>> Handle(
        GetUserOrdersQuery request,
        CancellationToken ct)
    {
        const string sql = @"
            SELECT Id, Status, TotalAmount, CreatedAt
            FROM Orders
            WHERE UserId = @UserId
            ORDER BY CreatedAt DESC
            OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY";
        
        var orders = await _connection.QueryAsync<OrderSummaryDto>(
            sql, new { request.UserId, request.Skip, request.Take });
        
        var total = await _connection.ExecuteScalarAsync<int>(
            "SELECT COUNT(*) FROM Orders WHERE UserId = @UserId",
            new { request.UserId });
        
        return new PagedResult<OrderSummaryDto>(orders.ToList(), total);
    }
}
```

### 4. Sử Dụng trong Controller

```csharp
[ApiController]
[Route("api/v{version:apiVersion}/orders")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _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));
    }
}
```

## Pipeline Behaviors

### Validation Behavior

```csharp
public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _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

```csharp
public class LoggingBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger _logger;
    
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        var requestName = typeof(TRequest).Name;
        _logger.LogInformation("Đang xử lý {RequestName}", requestName);
        
        var stopwatch = Stopwatch.StartNew();
        var response = await next();
        stopwatch.Stop();
        
        _logger.LogInformation(
            "Đã xử lý {RequestName} trong {ElapsedMs}ms",
            requestName, stopwatch.ElapsedMilliseconds);
        
        return response;
    }
}
```

### Đăng Ký Behaviors

```csharp
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});
```

## Lỗi Thường Gặp

### ❌ Dùng EF Core Cho Queries Đơn Giản

```csharp
// SAI: EF Core nặng cho queries
return await _context.Orders
    .Include(o => o.OrderItems)
    .Where(o => o.UserId == userId)
    .ToListAsync();
```

### ✅ Dùng Dapper Cho Đọc Tối Ưu

```csharp
// ĐÚNG: Dapper query nhẹ
return await _connection.QueryAsync<OrderDto>(
    "SELECT Id, Status, TotalAmount FROM Orders WHERE UserId = @UserId",
    new { userId });
```

## Skills Liên Quan

- [Thiết Kế API](./api-design.md) - Mẫu controller
- [Repository Pattern](./repository-pattern.md) - Truy xuất dữ liệu
- [Xử Lý Lỗi](./error-handling-patterns.md) - Validation errors
- [Mẫu Testing](./testing-patterns.md) - Handler testing

## Tài Nguyên Bổ Sung

- **Tài Liệu Skill Đầy Đủ**: [`.agent/skills/cqrs-mediatr/SKILL.md`](../../.agent/skills/cqrs-mediatr/SKILL.md)
- **Ví Dụ**: [`.agent/skills/cqrs-mediatr/references/REFERENCE.md`](../../.agent/skills/cqrs-mediatr/references/REFERENCE.md)

---

**English Version**: [CQRS & MediatR Patterns](../../en/skills/cqrs-mediatr.md)
