MediatR在CleanArchitecture中的应用:CQRS模式实现
引言:为什么选择MediatR和CQRS?
在现代企业级应用开发中,代码的可维护性和可扩展性至关重要。Clean Architecture(干净架构)通过分层设计解决了这一问题,而MediatR与CQRS(Command Query Responsibility Segregation,命令查询职责分离)模式的结合,则为这种架构提供了强大的实现工具。
你是否曾经遇到过:
- 控制器代码臃肿,包含大量业务逻辑
- 代码复用困难,相似的逻辑分散在各个地方
- 单元测试复杂,依赖关系难以mock
- 系统扩展性差,新增功能需要修改多处代码
本文将深入探讨MediatR在CleanArchitecture项目中的实际应用,展示如何通过CQRS模式构建清晰、可维护的.NET应用架构。
CleanArchitecture项目结构概览
CleanArchitecture采用经典的分层架构:
MediatR核心配置
在CleanArchitecture中,MediatR的配置集中在Web项目的配置文件中:
// MediatrConfigs.cs
public static class MediatrConfigs
{
public static IServiceCollection AddMediatrConfigs(this IServiceCollection services)
{
var mediatRAssemblies = new[]
{
Assembly.GetAssembly(typeof(Contributor)), // Core层
Assembly.GetAssembly(typeof(CreateContributorCommand)) // UseCases层
};
services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(mediatRAssemblies!))
.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>))
.AddScoped<IDomainEventDispatcher, MediatRDomainEventDispatcher>();
return services;
}
}
这种配置方式确保了:
- 自动发现所有命令和查询处理器
- 支持跨切割关注点的管道行为
- 集成领域事件分发机制
CQRS模式实现详解
命令(Command)实现
命令代表改变系统状态的操作,通常对应CREATE、UPDATE、DELETE操作:
// CreateContributorCommand.cs
public record CreateContributorCommand(string Name, string? PhoneNumber)
: Ardalis.SharedKernel.ICommand<Result<int>>;
// CreateContributorHandler.cs
public class CreateContributorHandler(IRepository<Contributor> _repository)
: ICommandHandler<CreateContributorCommand, Result<int>>
{
public async Task<Result<int>> Handle(CreateContributorCommand request,
CancellationToken cancellationToken)
{
var newContributor = new Contributor(request.Name);
if (!string.IsNullOrEmpty(request.PhoneNumber))
{
newContributor.SetPhoneNumber(request.PhoneNumber);
}
var createdItem = await _repository.AddAsync(newContributor, cancellationToken);
return createdItem.Id;
}
}
查询(Query)实现
查询代表读取系统状态的操作,不改变系统状态:
// ListContributorsQuery.cs
public record ListContributorsQuery(int? Skip, int? Take)
: IQuery<Result<IEnumerable<ContributorDTO>>>;
// ListContributorsHandler.cs
public class ListContributorsHandler(IListContributorsQueryService _query)
: IQueryHandler<ListContributorsQuery, Result<IEnumerable<ContributorDTO>>>
{
public async Task<Result<IEnumerable<ContributorDTO>>> Handle(
ListContributorsQuery request, CancellationToken cancellationToken)
{
var result = await _query.ListAsync();
return Result.Success(result);
}
}
Web API端点实现
在Web层,端点变得极其简洁,只负责接收请求和返回响应:
// Create.cs - Web API端点
public class Create(IMediator _mediator)
: Endpoint<CreateContributorRequest, CreateContributorResponse>
{
public override void Configure()
{
Post(CreateContributorRequest.Route);
AllowAnonymous();
}
public override async Task HandleAsync(
CreateContributorRequest request, CancellationToken cancellationToken)
{
var result = await _mediator.Send(
new CreateContributorCommand(request.Name!, request.PhoneNumber),
cancellationToken);
if (result.IsSuccess)
{
Response = new CreateContributorResponse(result.Value, request.Name!);
return;
}
}
}
领域事件处理
CleanArchitecture还集成了领域事件机制,通过MediatR实现事件分发:
// EventDispatchInterceptor.cs - EF Core拦截器
public class EventDispatchInterceptor(IDomainEventDispatcher domainEventDispatcher)
: SaveChangesInterceptor
{
public override async ValueTask<int> SavedChangesAsync(
SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken)
{
var context = eventData.Context;
if (context is not AppDbContext appDbContext)
{
return await base.SavedChangesAsync(eventData, result, cancellationToken);
}
// 检索所有有领域事件的跟踪实体
var entitiesWithEvents = appDbContext.ChangeTracker.Entries<HasDomainEventsBase>()
.Select(e => e.Entity)
.Where(e => e.DomainEvents.Any())
.ToArray();
// 分发并清除领域事件
await _domainEventDispatcher.DispatchAndClearEvents(entitiesWithEvents);
return await base.SavedChangesAsync(eventData, result, cancellationToken);
}
}
管道行为(Pipeline Behaviors)
MediatR支持管道行为,用于实现横切关注点:
// LoggingBehavior.cs示例
public class LoggingBehavior<TRequest, TResponse>(ILogger<TRequest> logger)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
logger.LogInformation("Handling {RequestName}", typeof(TRequest).Name);
var response = await next();
logger.LogInformation("Handled {RequestName}", typeof(TRequest).Name);
return response;
}
}
优势对比分析
| 特性 | 传统方式 | MediatR + CQRS方式 |
|---|---|---|
| 控制器复杂度 | 高,包含业务逻辑 | 低,只负责HTTP交互 |
| 代码复用性 | 差 | 优秀,命令/查询可复用 |
| 单元测试 | 复杂,需要mock HTTP上下文 | 简单,直接测试处理器 |
| 可维护性 | 随着功能增加急剧下降 | 良好,功能模块化 |
| 团队协作 | 容易冲突 | 按功能模块分工明确 |
最佳实践指南
1. 命令和查询设计原则
2. 错误处理策略
// 使用Result模式进行错误处理
public async Task<Result<int>> Handle(CreateContributorCommand request,
CancellationToken cancellationToken)
{
try
{
// 业务逻辑
return Result.Success(result);
}
catch (Exception ex)
{
return Result.Error(ex.Message);
}
}
3. 性能优化建议
- 使用
IRequest<T>和IRequest区分有返回值和无返回值操作 - 合理使用缓存装饰器模式
- 避免在查询处理器中进行复杂的数据转换
实际应用场景
场景1:用户注册流程
// RegisterUserCommand.cs
public record RegisterUserCommand(string Email, string Password, string Name)
: ICommand<Result<Guid>>;
// RegisterUserHandler.cs
public class RegisterUserHandler : ICommandHandler<RegisterUserCommand, Result<Guid>>
{
public async Task<Result<Guid>> Handle(RegisterUserCommand request,
CancellationToken cancellationToken)
{
// 验证邮箱唯一性
// 密码加密
// 创建用户实体
// 发送欢迎邮件
// 返回用户ID
}
}
场景2:复杂报表查询
// SalesReportQuery.cs
public record SalesReportQuery(DateTime StartDate, DateTime EndDate,
string Region, int? CategoryId) : IQuery<Result<SalesReportDto>>;
// SalesReportHandler.cs
public class SalesReportHandler : IQueryHandler<SalesReportQuery, Result<SalesReportDto>>
{
public async Task<Result<SalesReportDto>> Handle(SalesReportQuery request,
CancellationToken cancellationToken)
{
// 多表关联查询
// 数据聚合计算
// 生成报表DTO
}
}
总结与展望
MediatR与CQRS模式在CleanArchitecture中的结合,为.NET应用开发带来了革命性的改进:
- 架构清晰度:明确的分层和责任分离
- 可测试性:每个命令/查询都可以独立测试
- 可维护性:功能模块化,易于理解和修改
- 可扩展性:新增功能只需添加新的命令/查询处理器
随着.NET生态的不断发展,这种模式将继续演进,特别是在以下方向:
- 与微服务架构的更深度集成
- 云原生应用的支持优化
- 性能监控和诊断工具的完善
通过本文的深入分析,相信你已经掌握了MediatR在CleanArchitecture中实现CQRS模式的核心要点。在实际项目中应用这些模式,将显著提升你的代码质量和开发效率。
下一步行动建议:
- 在现有项目中尝试引入MediatR
- 从简单的CRUD操作开始实践CQRS
- 逐步重构复杂业务逻辑为命令/查询模式
- 建立团队内的编码规范和最佳实践
记住,架构的演进是一个持续的过程,关键是找到适合你项目规模和团队能力的平衡点。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



