You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 01:01:19