Insert方法返回对象是否违反CQRS模式?BDD测试如何获取新增数据?
问题解答
核心结论
修改命令处理器返回创建后的实体ID(或仅返回ID)并不违反CQRS核心原则。CQRS的核心是分离命令(写操作)与查询(读操作)的职责,而非禁止命令返回任何值——命令返回写操作产生的元数据(如生成的ID、操作状态)是完全合理的,这不属于查询逻辑范畴。
可行解决方案
方案1:让命令返回生成的ID
这是最直接高效的方式,调整命令定义与处理器,返回创建后的QuestionId,测试时直接用该ID验证数据持久化:
// 命令定义 public class CreateQuestionCommand : IRequest<Guid> { public string Title { get; set; } public string Content { get; set; } } // 命令处理器 public class CreateQuestionCommandHandler : IRequestHandler<CreateQuestionCommand, Guid> { private readonly AppDbContext _dbContext; public CreateQuestionCommandHandler(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<Guid> Handle(CreateQuestionCommand request, CancellationToken cancellationToken) { var question = new Question(request.Title, request.Content); // 构造函数生成Id _dbContext.Questions.Add(question); await _dbContext.SaveChangesAsync(cancellationToken); return question.Id; } }
测试流程中,When步骤发送命令后获取ID,Then步骤用该ID执行查询验证数据是否存在。
方案2:测试环境注入自定义ID生成逻辑
若坚持命令不返回任何值,可将ID生成逻辑抽象为服务,在测试环境预设ID:
// 抽象ID生成器 public interface IIdGenerator { Guid Generate(); } // 生产环境实现 public class DefaultIdGenerator : IIdGenerator { public Guid Generate() => Guid.NewGuid(); } // 测试环境实现(可预设ID) public class TestIdGenerator : IIdGenerator { public Guid PresetId { get; set; } public Guid Generate() => PresetId; }
修改Question构造函数依赖注入IIdGenerator:
public class Question { public Guid Id { get; } public string Title { get; } public string Content { get; } public Question(string title, string content, IIdGenerator idGenerator) { Id = idGenerator.Generate(); Title = title; Content = content; } }
测试时先设置TestIdGenerator的PresetId,发送命令后直接用该预设ID查询验证。
方案3:通过领域事件捕获ID
若系统已使用领域事件,可在Question创建时发布QuestionCreatedEvent,测试时订阅事件获取ID:
// 领域事件定义 public class QuestionCreatedEvent : INotification { public Guid QuestionId { get; } public QuestionCreatedEvent(Guid questionId) => QuestionId = questionId; } // Question构造函数中添加事件 public class Question { public Guid Id { get; } private readonly List<INotification> _domainEvents = new(); public Question(string title, string content) { Id = Guid.NewGuid(); Title = title; Content = content; _domainEvents.Add(new QuestionCreatedEvent(Id)); } public IEnumerable<INotification> GetDomainEvents() => _domainEvents; } // 命令处理器中发布事件 public class CreateQuestionCommandHandler : IRequestHandler<CreateQuestionCommand> { private readonly AppDbContext _dbContext; private readonly IMediator _mediator; public CreateQuestionCommandHandler(AppDbContext dbContext, IMediator mediator) { _dbContext = dbContext; _mediator = mediator; } public async Task Handle(CreateQuestionCommand request, CancellationToken cancellationToken) { var question = new Question(request.Title, request.Content); _dbContext.Questions.Add(question); await _dbContext.SaveChangesAsync(cancellationToken); // 发布领域事件 foreach (var @event in question.GetDomainEvents()) { await _mediator.Publish(@event, cancellationToken); } } }
测试时注册临时事件处理器,捕获QuestionCreatedEvent并提取ID用于后续验证。
关于CQRS合规性的补充说明
CQRS禁止的是命令中包含查询逻辑(比如在命令处理器中读取其他实体数据并返回),而返回命令自身产生的元数据(如ID、操作成功标识)完全符合CQRS的职责分离原则——你只是传递写操作的结果,而非执行读操作。
内容的提问来源于stack exchange,提问作者Ali.Rashidi
相关产品推荐
相关产品推荐

