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

重构.NET Core应用:单命令中创建关联DDD聚合根问题咨询

处理多聚合根创建的可行方案

方案1:单个命令处理器内按顺序创建(带事务)

直接在主命令处理器中按依赖顺序创建三个聚合根,全程用事务包裹保证原子性,避免部分创建成功的情况。

示例代码:

public class CreateExamWithEntitiesCommandHandler : IRequestHandler<CreateExamWithEntitiesCommand, Guid>
{
    private readonly AppDbContext _dbContext;

    public CreateExamWithEntitiesCommandHandler(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<Guid> Handle(CreateExamWithEntitiesCommand request, CancellationToken cancellationToken)
    {
        using var transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken);
        try
        {
            // 创建Patient并持久化
            var patient = new Patient(request.PatientName, request.PatientDob);
            _dbContext.Patients.Add(patient);
            await _dbContext.SaveChangesAsync(cancellationToken);

            // 创建ServiceProvider并持久化
            var serviceProvider = new ServiceProvider(request.SpName, request.SpLicense);
            _dbContext.ServiceProviders.Add(serviceProvider);
            await _dbContext.SaveChangesAsync(cancellationToken);

            // 用已生成的ID创建Exam
            var exam = new Exam(patient.Id, serviceProvider.Id, request.ExamDate);
            _dbContext.Exams.Add(exam);
            await _dbContext.SaveChangesAsync(cancellationToken);

            await transaction.CommitAsync(cancellationToken);
            return exam.Id;
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }
}
  • 优点:逻辑集中直观,事务保证数据一致性,无需额外拆分组件。
  • 缺点:处理器职责较重,后续若聚合根创建逻辑变复杂,会导致代码臃肿。

方案2:拆分嵌套命令(利用Mediatr调度)

把Patient、ServiceProvider的创建逻辑拆成独立的子命令和处理器,主处理器通过Mediatr调度这些子命令,再完成Exam的创建。

示例代码:

// 子命令:创建Patient
public record CreatePatientCommand(string Name, DateTime Dob) : IRequest<Guid>;

public class CreatePatientCommandHandler : IRequestHandler<CreatePatientCommand, Guid>
{
    private readonly AppDbContext _dbContext;

    public CreatePatientCommandHandler(AppDbContext dbContext) => _dbContext = dbContext;

    public async Task<Guid> Handle(CreatePatientCommand request, CancellationToken cancellationToken)
    {
        var patient = new Patient(request.Name, request.Dob);
        _dbContext.Patients.Add(patient);
        await _dbContext.SaveChangesAsync(cancellationToken);
        return patient.Id;
    }
}

// 主命令处理器
public class CreateExamWithEntitiesCommandHandler : IRequestHandler<CreateExamWithEntitiesCommand, Guid>
{
    private readonly IMediator _mediator;
    private readonly AppDbContext _dbContext;

    public CreateExamWithEntitiesCommandHandler(IMediator mediator, AppDbContext dbContext)
    {
        _mediator = mediator;
        _dbContext = dbContext;
    }

    public async Task<Guid> Handle(CreateExamWithEntitiesCommand request, CancellationToken cancellationToken)
    {
        using var transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken);
        try
        {
            // 调度子命令获取ID
            var patientId = await _mediator.Send(new CreatePatientCommand(request.PatientName, request.PatientDob), cancellationToken);
            var spId = await _mediator.Send(new CreateServiceProviderCommand(request.SpName, request.SpLicense), cancellationToken);

            // 创建Exam
            var exam = new Exam(patientId, spId, request.ExamDate);
            _dbContext.Exams.Add(exam);
            await _dbContext.SaveChangesAsync(cancellationToken);

            await transaction.CommitAsync(cancellationToken);
            return exam.Id;
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }
}
  • 优点:每个处理器职责单一,符合单一原则,后续维护和复用子命令更方便。
  • 缺点:需要手动管理事务范围,确保所有操作在同一事务内。

方案3:领域服务协调创建

如果聚合根创建包含复杂领域规则,引入领域服务封装协调逻辑,命令处理器仅负责调用服务。

示例代码:

public class ExamDomainService
{
    private readonly AppDbContext _dbContext;

    public ExamDomainService(AppDbContext dbContext) => _dbContext = dbContext;

    public async Task<Guid> CreateExamWithRelations(string patientName, DateTime patientDob, string spName, string spLicense, DateTime examDate, CancellationToken cancellationToken)
    {
        // 领域规则校验:比如检查ServiceProvider许可证是否有效
        if (string.IsNullOrWhiteSpace(spLicense))
            throw new InvalidOperationException("ServiceProvider许可证不能为空");

        var patient = new Patient(patientName, patientDob);
        _dbContext.Patients.Add(patient);
        await _dbContext.SaveChangesAsync(cancellationToken);

        var serviceProvider = new ServiceProvider(spName, spLicense);
        _dbContext.ServiceProviders.Add(serviceProvider);
        await _dbContext.SaveChangesAsync(cancellationToken);

        var exam = new Exam(patient.Id, serviceProvider.Id, examDate);
        _dbContext.Exams.Add(exam);
        await _dbContext.SaveChangesAsync(cancellationToken);

        return exam.Id;
    }
}

// 命令处理器
public class CreateExamWithEntitiesCommandHandler : IRequestHandler<CreateExamWithEntitiesCommand, Guid>
{
    private readonly ExamDomainService _domainService;
    private readonly AppDbContext _dbContext;

    public CreateExamWithEntitiesCommandHandler(ExamDomainService domainService, AppDbContext dbContext)
    {
        _domainService = domainService;
        _dbContext = dbContext;
    }

    public async Task<Guid> Handle(CreateExamWithEntitiesCommand request, CancellationToken cancellationToken)
    {
        using var transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken);
        try
        {
            var examId = await _domainService.CreateExamWithRelations(request.PatientName, request.PatientDob, request.SpName, request.SpLicense, request.ExamDate, cancellationToken);
            await transaction.CommitAsync(cancellationToken);
            return examId;
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }
}
  • 优点:领域规则集中封装,符合DDD思想,命令处理器仅做调度,职责清晰。
  • 缺点:增加了服务层,需确保领域服务的职责边界清晰,避免过度封装。

关键注意事项

  • 事务一致性:无论采用哪种方案,必须保证三个聚合根的创建在同一个事务中,防止数据不一致。
  • 聚合根边界校验:再次确认三个实体是否为独立聚合根——比如Patient是否会被其他业务场景引用?如果仅为Exam的附属信息,可将其改为Exam的内部实体,简化逻辑。
  • 重复创建校验:若请求可能包含已存在的Patient或ServiceProvider,需先通过唯一标识(如身份证号、许可证号)查询数据库,避免重复创建。

内容的提问来源于stack exchange,提问作者lasjan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 03:37:34