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

在实体关联繁多场景下,如何用DDD避免性能大幅损耗?

兼顾DDD优势与性能的薪资发放方案优化

在处理大规模关联实体的DDD场景时,既要保持领域逻辑的封装性,又要避免内存和性能损耗,确实是常见的设计难题。结合你给出的薪资发放场景,我们可以从以下角度优化方案,同时兼顾DDD的核心优势:


原方案问题复盘

  • 方案1(数据驱动):业务逻辑硬编码在SQL中,领域规则分散在数据层,完全违背DDD「领域逻辑中心化」的原则,后续规则变更维护成本极高,也无法利用实体封装做内存校验。
  • 方案2(纯DDD实体封装):虽严格遵循DDD规范,但加载全量员工到内存的方式,在员工规模较大时会引发内存溢出、数据库查询耗时过长的问题,不具备生产可行性。
  • 方案3(混合模式):思路方向正确,但存在冗余逻辑——内存中重复执行薪资发放逻辑,同时调用数据库命令,既增加了不必要的内存计算,还可能引发内存状态与数据库状态不一致的风险。

优化方案1:领域服务+批量数据库操作(DDD兼容)

核心思路是将薪资发放的核心校验逻辑放在领域服务中,仅加载必要的聚合根数据和统计数据,同时利用数据库批量操作避免全量数据加载:

领域服务实现

public class CompanySalaryService
{
    private readonly ICompanyRepository _companyRepo;
    private readonly IEmployeeRepository _employeeRepo;

    public CompanySalaryService(ICompanyRepository companyRepo, IEmployeeRepository employeeRepo)
    {
        _companyRepo = companyRepo;
        _employeeRepo = employeeRepo;
    }

    public async Task PaySalariesAsync(Guid companyId)
    {
        // 仅加载Company聚合根,不加载关联员工
        var company = await _companyRepo.GetByIdAsync(companyId);
        if (company == null)
            throw new ArgumentException("Company not found");

        // 直接在数据库统计总薪资,避免加载全量员工
        var totalSalary = await _employeeRepo.GetTotalSalaryByCompanyIdAsync(companyId);

        // 领域规则校验:余额是否足够
        if (totalSalary > company.Balance)
            throw new InvalidOperationException("Not enough balance to pay employees salary!");

        // 事务保证操作原子性
        using var transaction = await _companyRepo.BeginTransactionAsync();
        try
        {
            // 更新公司余额
            company.Balance -= totalSalary;
            await _companyRepo.UpdateAsync(company);

            // 批量更新员工余额(直接操作数据库)
            await _employeeRepo.PaySalariesToCompanyEmployeesAsync(companyId);

            await transaction.CommitAsync();
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

持久层关键实现

// EmployeeRepository 部分方法
public async Task<decimal> GetTotalSalaryByCompanyIdAsync(Guid companyId)
{
    const string sql = "SELECT SUM(Salary) FROM Employee WHERE CompanyId = @CompanyId";
    using var connection = new SqlConnection(Constants.ConnectionString);
    return await connection.QueryFirstOrDefaultAsync<decimal>(sql, new { CompanyId = companyId });
}

public async Task PaySalariesToCompanyEmployeesAsync(Guid companyId)
{
    const string sql = "UPDATE Employee SET Balance = Balance + Salary WHERE CompanyId = @CompanyId";
    using var connection = new SqlConnection(Constants.ConnectionString);
    await connection.ExecuteAsync(sql, new { CompanyId = companyId });
}

优化方案2:DDD命令模式+前置校验

将薪资发放作为领域命令,由基础设施层执行批量操作,但领域层负责前置规则校验,完全避免加载关联实体:

领域命令与校验器

public record PaySalariesCommand(Guid CompanyId);

public class PaySalariesCommandValidator
{
    private readonly ICompanyRepository _companyRepo;
    private readonly IEmployeeRepository _employeeRepo;

    public PaySalariesCommandValidator(ICompanyRepository companyRepo, IEmployeeRepository employeeRepo)
    {
        _companyRepo = companyRepo;
        _employeeRepo = employeeRepo;
    }

    public async Task ValidateAsync(PaySalariesCommand command)
    {
        var company = await _companyRepo.GetByIdAsync(command.CompanyId);
        if (company == null)
            throw new ArgumentException("Company not found");

        var totalSalary = await _employeeRepo.GetTotalSalaryByCompanyIdAsync(command.CompanyId);
        if (totalSalary > company.Balance)
            throw new InvalidOperationException("Not enough balance to pay employees salary!");
    }
}

命令处理器(基础设施层)

public class PaySalariesCommandHandler
{
    private readonly PaySalariesCommandValidator _validator;
    private readonly IDbConnectionFactory _dbConnectionFactory;

    public PaySalariesCommandHandler(PaySalariesCommandValidator validator, IDbConnectionFactory dbConnectionFactory)
    {
        _validator = validator;
        _dbConnectionFactory = dbConnectionFactory;
    }

    public async Task HandleAsync(PaySalariesCommand command)
    {
        // 先执行领域规则校验
        await _validator.ValidateAsync(command);

        // 执行批量数据库操作
        using var connection = await _dbConnectionFactory.CreateConnectionAsync();
        using var transaction = await connection.BeginTransactionAsync();
        try
        {
            // 更新公司余额
            const string updateCompanySql = @"
                UPDATE Company 
                SET Balance = Balance - (SELECT SUM(Salary) FROM Employee WHERE CompanyId = @CompanyId)
                WHERE Id = @CompanyId";
            await connection.ExecuteAsync(updateCompanySql, new { command.CompanyId }, transaction);

            // 更新员工余额
            const string updateEmployeeSql = @"
                UPDATE Employee 
                SET Balance = Balance + Salary 
                WHERE CompanyId = @CompanyId";
            await connection.ExecuteAsync(updateEmployeeSql, new { command.CompanyId }, transaction);

            await transaction.CommitAsync();
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

关键设计原则总结

  1. 避免过度加载聚合关联:DDD聚合根设计不要求每次加载所有关联实体,批量操作场景应直接利用数据库的统计和批量更新能力;
  2. 领域逻辑中心化:无论采用哪种方案,业务规则(如余额校验)必须放在领域层,不能分散在数据层;
  3. 利用数据库特性:数据库的批量更新、事务、统计查询是解决大规模数据操作性能问题的最优手段,无需强行在内存中模拟;
  4. 分离命令与查询:遵循CQRS原则,薪资发放是命令操作,无需返回大量数据,直接利用数据库命令完成即可。

内容的提问来源于stack exchange,提问作者Pedro Faria Fernandes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:09:55