.NET Core 中的高级 CQRS 和中介模式

作者:微信公众号:【架构师老卢】
9-17 18:24
127

1. 向 CQRS 命令添加验证

处理命令的常见要求之一是输入验证。如果没有适当的验证,无效的命令可能会影响应用程序的状态。在这里,我们将集成 FluentValidation,这是一个用于声明性验证的常用 .NET 库。

步骤 1.1:安装 FluentValidation

首先安装 FluentValidation 包:

dotnet add package FluentValidation  
dotnet add package FluentValidation.DependencyInjectionExtensions

步骤 1.2:为命令定义验证器

让我们为 .这可确保任何商品创建请求在处理之前都遵守特定规则。CreateProductCommand

using FluentValidation;  
  
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>  
{  
    public CreateProductCommandValidator()  
    {  
        RuleFor(x => x.Name).NotEmpty().WithMessage("Product name is required.");  
        RuleFor(x => x.Price).GreaterThan(0).WithMessage("Product price must be greater than zero.");  
    }  
}

步骤 1.3:在 CQRS 管道中集成验证程序

接下来,我们将此验证器集成到 MediatR 管道中,以便每个命令在到达处理程序之前都经过验证。

在:Program.cs

builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());  
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

现在,定义运行验证器的 :ValidationBehavior

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, 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 cancellationToken)
    {
        var context = new ValidationContext<TRequest>(request);
        var validationFailures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (validationFailures.Any())
        {
            throw new ValidationException(validationFailures);
        }

        return await next();
    }
}

2. 处理横切关注点

在实际应用程序中,您通常需要解决横切关注点,例如日志记录缓存事务管理。MediatR 允许我们通过管道行为优雅地处理这些问题。

步骤 2.1:记录请求

让我们实现一个简单的日志记录行为来记录所有传入的命令和查询:

public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, 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 cancellationToken)
    {
        _logger.LogInformation($"Handling {typeof(TRequest).Name}");
        var response = await next();
        _logger.LogInformation($"Handled {typeof(TRequest).Name}");
        return response;
    }
}

您现在可以在文件中注册此行为:Program.cs

builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));

3. 事件驱动架构:处理域事件

使用 CQRS 时,处理域事件是一项常见要求。例如,在创建产品时,您可能希望触发其他操作,例如发送通知或更新外部系统。

步骤 3.1:定义域事件

让我们定义一个在成功创建产品后引发的 a:ProductCreatedEvent

public class ProductCreatedEvent : INotification  
{  
    public int ProductId { get; }  
  
    public ProductCreatedEvent(int productId)  
    {  
        ProductId = productId;  
    }  
}

步骤 3.2:在命令处理程序中发布事件

修改 以引发事件:CreateProductCommandHandler

public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
    private readonly ApplicationDbContext _context;
    private readonly IMediator _mediator;

    public CreateProductCommandHandler(ApplicationDbContext context, IMediator mediator)
    {
        _context = context;
        _mediator = mediator;
    }

    public async Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = new Product { Name = request.Name, Price = request.Price };
        _context.Products.Add(product);
        await _context.SaveChangesAsync(cancellationToken);

        // Publish domain event
        await _mediator.Publish(new ProductCreatedEvent(product.Id));

        return product.Id;
    }
}

步骤 3.3:处理 Domain 事件

现在,定义一个处理程序来响应 :ProductCreatedEvent

public class ProductCreatedEventHandler : INotificationHandler<ProductCreatedEvent>
{
    private readonly ILogger<ProductCreatedEventHandler> _logger;

    public ProductCreatedEventHandler(ILogger<ProductCreatedEventHandler> logger)
    {
        _logger = logger;
    }

    public Task Handle(ProductCreatedEvent notification, CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Product with ID {notification.ProductId} has been created.");
        return Task.CompletedTask;
    }
}

4. 查询性能优化

CQRS 允许您独立于命令优化查询。您可以使用缓存、读取优化数据库甚至投影来提高性能。

步骤 4.1:缓存查询

以下是如何向查询处理程序添加缓存的示例:

public class CachedGetProductByIdHandler : IRequestHandler<GetProductByIdQuery, Product>
{
    private readonly IMemoryCache _cache;
    private readonly ApplicationDbContext _context;

    public CachedGetProductByIdHandler(IMemoryCache cache, ApplicationDbContext context)
    {
        _cache = cache;
        _context = context;
    }

    public async Task<Product> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
    {
        return await _cache.GetOrCreateAsync($"Product_{request.Id}", async entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromMinutes(10);
            return await _context.Products.FindAsync(request.Id);
        });
    }
}

在本文中,我们探讨了在 ASP.NET Core 中使用 Mediator 模式的高级 CQRS 技术。我们学习了如何实现输入验证、处理管道行为的横切关注点,以及使用域事件来启用事件驱动的架构。最后,我们介绍了如何使用缓存优化查询性能。

借助这些高级技术,您的 CQRS 实施已准备好用于实际生产,确保您的 ASP.NET Core 应用程序保持可扩展性、可维护性和高性能。

相关留言评论
昵称:
邮箱:
阅读排行