CQRS কী?
Command Query Responsibility Segregation — Read (Query) ও Write (Command) আলাদা model/handler-এ থাকে। Complex domain-এ scale ভালো।
MediatR Install
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
// Program.cs
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
Query Example
// Query
public record GetProductByIdQuery(int Id) : IRequest<ProductDto?>;
// Handler
public class GetProductByIdHandler : IRequestHandler<GetProductByIdQuery, ProductDto?> {
private readonly AppDbContext _ctx;
public GetProductByIdHandler(AppDbContext ctx) => _ctx = ctx;
public async Task<ProductDto?> Handle(GetProductByIdQuery req, CancellationToken ct) {
var p = await _ctx.Products.FindAsync(new object[]{ req.Id }, ct);
return p == null ? null : new ProductDto(p.Id, p.Name, p.Price);
}
}
Command Example
public record CreateProductCommand(string Name, decimal Price) : IRequest<int>;
public class CreateProductHandler : IRequestHandler<CreateProductCommand, int> {
private readonly AppDbContext _ctx;
public CreateProductHandler(AppDbContext ctx) => _ctx = ctx;
public async Task<int> Handle(CreateProductCommand req, CancellationToken ct) {
var p = new Product { Name = req.Name, Price = req.Price };
_ctx.Products.Add(p);
await _ctx.SaveChangesAsync(ct);
return p.Id;
}
}
Controller — Slim
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase {
private readonly IMediator _mediator;
public ProductsController(IMediator m) => _mediator = m;
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id) {
var dto = await _mediator.Send(new GetProductByIdQuery(id));
return dto == null ? NotFound() : Ok(dto);
}
[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand cmd) {
var id = await _mediator.Send(cmd);
return CreatedAtAction(nameof(Get), new { id }, null);
}
}
Pipeline Behavior (Validation/Logging)
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> {
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> v) => _validators = v;
public async Task<TResponse> Handle(TRequest req, RequestHandlerDelegate<TResponse> next, CancellationToken ct) {
var ctx = new ValidationContext<TRequest>(req);
var failures = (await Task.WhenAll(_validators.Select(v => v.ValidateAsync(ctx, ct))))
.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Any()) throw new ValidationException(failures);
return await next();
}
}