重构.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
相关产品推荐
相关产品推荐

