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

DDD领域驱动设计结合CQRS模式下批量导入逻辑复用与性能优化问询

解决方案核心思路

你的核心问题根源是业务逻辑耦合在了Command Handler层,而Handler本身是用来处理请求协调的薄适配层,不是承载业务规则的地方。只要把业务逻辑下沉到领域层(聚合根/领域服务),就能同时满足复用性和性能要求,也完全符合CQRS不允许嵌套命令的规范。


步骤1:重构现有代码,把业务逻辑从Handler下沉到聚合根

你原来的Business Logic A/B/C全部是购物清单这个聚合根的固有业务规则,应该放到ShoppingList聚合根内部实现,Handler只负责参数传递、聚合读取、提交变更三个动作。

聚合根代码示例

public class ShoppingList : AggregateRoot
{
    public Guid UserId { get; private set; }
    private List<ShoppingItem> _items = new();
    public IReadOnlyCollection<ShoppingItem> Items => _items.AsReadOnly();

    // 原CreateShoppingList的业务逻辑A收敛至此
    public static ShoppingList Create(Guid userId)
    {
        if (userId == Guid.Empty) throw new ArgumentException("用户ID无效");
        // 后续新增的创建类校验规则都可以加在这里
        return new ShoppingList { UserId = userId };
    }

    // 原AddItemToShoppingList的业务逻辑B收敛至此
    public void AddItem(Guid productId, int quantity)
    {
        if (quantity <= 0) throw new ArgumentException("商品数量必须大于0");
        var existingItem = _items.FirstOrDefault(x => x.ProductId == productId);
        if (existingItem != null)
        {
            // 已存在商品直接复用更新逻辑
            UpdateItem(productId, existingItem.Quantity + quantity);
            return;
        }
        _items.Add(new ShoppingItem(productId, quantity));
    }

    // 原UpdateItemOnShoppingList的业务逻辑C收敛至此
    public void UpdateItem(Guid productId, int quantity)
    {
        if (quantity <= 0) throw new ArgumentException("商品数量必须大于0");
        var existingItem = _items.FirstOrDefault(x => x.ProductId == productId) 
            ?? throw new InvalidOperationException("商品不存在于购物清单");
        existingItem.UpdateQuantity(quantity);
    }
}

重构后的单操作Handler代码

public class ShoppingListCommandHandler : 
ICommandHandler<CreateShoppingListCommand>,
ICommandHandler<AddItemToShoppingListCommand>,
ICommandHandler<UpdateItemOnShoppingListCommand> {
    
    Task Handle(CreateShoppingListCommand command, CancellationToken ct) {
        var list = ShoppingList.Create(command.UserId);
        _shoppingListRepository.Add(list);
        _shoppingListRepository.SaveChangesAsync(ct);      
    }

    Task Handle(AddItemToShoppingListCommand command, CancellationToken ct) {
        var list = await _shoppingListRepository.GetByUserIdAsync(command.UserId, ct);
        if (list == null) throw new InvalidOperationException("购物清单不存在");
        list.AddItem(command.ProductId, command.Quantity);
        _shoppingListRepository.SaveChangesAsync(ct);      
    }

    Task Handle(UpdateItemOnShoppingListCommand command, CancellationToken ct) {
        var list = await _shoppingListRepository.GetByUserIdAsync(command.UserId, ct);
        if (list == null) throw new InvalidOperationException("购物清单不存在");
        list.UpdateItem(command.ProductId, command.Quantity);
        _shoppingListRepository.SaveChangesAsync(ct);      
    }
}

步骤2:重写导入逻辑,批量预加载数据+复用领域层逻辑

导入逻辑不需要自定义业务规则,也不需要发送单个Command,只需要批量预加载所有需要的数据到内存,直接调用聚合根的方法处理,最后一次性提交即可。

导入Handler代码示例

Task Handle(ImportShoppingListCommand command, CancellationToken ct) {
    // 1. 基础文件格式校验、解析行数据
    var importRows = ParseAndValidateFile(command.FileContent);
    if (!importRows.Any()) return Task.CompletedTask;

    // 2. 批量校验用户合法性,仅1次DB查询
    var userIds = importRows.Select(x => x.UserId).Distinct().ToList();
    var validUserIds = await _userRepository.GetExistsUserIdsAsync(userIds, ct);
    var validRows = importRows.Where(x => validUserIds.Contains(x.UserId)).ToList();

    // 3. 批量拉取所有有效用户的现有购物清单,仅1次DB查询
    var existingLists = await _shoppingListRepository.GetListByUserIdsAsync(validUserIds, ct);
    var listDict = existingLists.ToDictionary(x => x.UserId);

    // 4. 内存聚合重复行:同一用户同一商品合并数量,避免重复处理
    var groupedRows = validRows.GroupBy(x => new {x.UserId, x.ProductId})
        .Select(g => new {
            g.Key.UserId,
            g.Key.ProductId,
            TotalQuantity = g.Sum(x => x.Quantity)
        }).ToList();

    // 5. 遍历处理所有行,全部复用聚合根的业务逻辑
    foreach (var row in groupedRows)
    {
        if (!listDict.TryGetValue(row.UserId, out var shoppingList))
        {
            // 不存在购物清单则创建,复用创建逻辑
            shoppingList = ShoppingList.Create(row.UserId);
            _shoppingListRepository.Add(shoppingList);
            listDict.Add(row.UserId, shoppingList);
        }
        // 添加商品,内部自动处理新增/更新逻辑,复用原有规则
        shoppingList.AddItem(row.ProductId, row.TotalQuantity);
    }

    // 6. 一次性提交所有变更,仅1次DB写入
    await _shoppingListRepository.SaveChangesAsync(ct);
}

方案优势

  • 逻辑唯一:所有业务规则全部收敛在领域层,单接口操作和批量导入共用同一套逻辑,后续规则修改只需要改动一处
  • 性能达标:整个导入流程仅需要3次DB交互,没有循环IO开销,也不需要生成大量Command实例
  • 符合规范:没有嵌套发送Command,完全符合你的CQRS约束
  • 天然处理重复行:内存层先按用户+商品维度合并导入数据,避免重复操作的问题

扩展场景兼容

如果后续新增跨聚合的业务规则(比如校验每个家庭仅能有一个购物清单),只需要把这部分逻辑抽到公共的领域服务中,单操作Handler和导入Handler都调用同一个领域服务的校验方法即可,依然保持唯一的逻辑维护入口。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 07:15:03