# Integration Standard: CQRS with MediatR

## Overview
Commands mutate state. Queries read state. Never mix them.

## Setup
```xml
<PackageReference Include="MediatR" Version="12.*" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.*" />
```

```csharp
// Program.cs
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
```

## Command Pattern
```csharp
// Command (mutates state, returns result)
public record CreateOrderCommand(Guid UserId, List<OrderItem> Items)
    : IRequest<CreateOrderResult>;

public record CreateOrderResult(Guid OrderId, decimal Total);

// Handler
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, CreateOrderResult>
{
    public async Task<CreateOrderResult> Handle(CreateOrderCommand cmd, CancellationToken ct)
    {
        var order = Order.Create(cmd.UserId, cmd.Items);
        await _repository.AddAsync(order, ct);
        await _unitOfWork.SaveChangesAsync(ct);

        // Publish domain event
        await _publisher.Publish(new OrderCreatedEvent(order.Id), ct);

        return new CreateOrderResult(order.Id, order.Total);
    }
}
```

## Query Pattern
```csharp
// Query (reads only, no side effects)
public record GetOrderQuery(Guid OrderId) : IRequest<OrderDto?>;

// Handler — use read model, not domain entity
public class GetOrderHandler : IRequestHandler<GetOrderQuery, OrderDto?>
{
    public async Task<OrderDto?> Handle(GetOrderQuery query, CancellationToken ct)
    {
        return await _context.Orders
            .Where(o => o.Id == query.OrderId)
            .Select(o => new OrderDto(o.Id, o.Status, o.Total))
            .FirstOrDefaultAsync(ct);
    }
}
```

## Pipeline Behaviors (Cross-Cutting)
```csharp
// Validation behavior
public class ValidationBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    public async Task<TResponse> Handle(TRequest request,
        RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        var failures = _validators
            .SelectMany(v => v.Validate(request).Errors)
            .Where(f => f != null)
            .ToList();

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

        return await next();
    }
}
```

## DI Registration Order
```csharp
// 1. MediatR (includes handler scanning)
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
});
// 2. FluentValidation
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
```

## Rules
- Commands return results, not void (enables error handling)
- Queries use DTOs, never expose domain entities
- Never call command handlers from query handlers
- One handler per Command/Query (no inheritance hierarchies)
