DDD实践答疑:新增需求时如何避免领域逻辑漏洞?
解决DDD中客户积分调整的领域逻辑漏洞问题
核心思路是明确区分两种积分扣除场景:用户主动兑换(需要250积分门槛)和订单修改导致的系统调整(无门槛但需保证积分不为负),同时通过封装和访问控制避免方法被误用。
1. 语义化命名+访问控制区分方法
不要新增通用的ReduceLoyaltyPoint,而是给订单调整场景专门创建一个语义明确的方法,比如AdjustLoyaltyPointForOrderModification,并通过C#的访问修饰符限制其调用范围:
- 用
internal修饰该方法,确保只有同一领域程序集内的代码(比如Order Aggregate)能调用,外部应用层无法直接访问,从根源上避免被用来替代兑换操作。 - 公开的
RedeemLoyaltyPoint保留原有的250积分限制,只用于用户主动兑换场景。
2. 代码实现示例
// 积分值对象:封装积分的基本加减逻辑,确保积分非负 public record LoyaltyPoint(int Value) { public static LoyaltyPoint Create(int value) { if (value < 0) throw new ArgumentException("积分不能为负数"); return new LoyaltyPoint(value); } public LoyaltyPoint Add(int amount) => Create(Value + amount); public LoyaltyPoint Subtract(int amount) => Create(Value - amount); } // 客户实体:封装积分的业务规则 public class CustomerEntity { private LoyaltyPoint _loyaltyPoint; public CustomerEntity(LoyaltyPoint initialPoint) { _loyaltyPoint = initialPoint; } // 公开方法:下单增加积分 public void AddLoyaltyPoint(int amount) { _loyaltyPoint = _loyaltyPoint.Add(amount); } // 公开方法:用户主动兑换积分,需满足250积分门槛 public void RedeemLoyaltyPoint(int amount) { if (_loyaltyPoint.Value < 250) throw new InvalidOperationException("积分不足250,无法发起兑换"); if (amount > _loyaltyPoint.Value) throw new InvalidOperationException("兑换积分不能超过当前持有量"); _loyaltyPoint = _loyaltyPoint.Subtract(amount); } // 内部方法:仅用于订单修改的积分调整,无兑换门槛,但保证积分非负 internal void AdjustLoyaltyPointForOrderModification(int adjustmentAmount) { var newPointValue = _loyaltyPoint.Value + adjustmentAmount; if (newPointValue < 0) throw new InvalidOperationException("调整后积分不能为负数"); _loyaltyPoint = LoyaltyPoint.Create(newPointValue); } } // 订单聚合根:处理订单生命周期中的积分逻辑 public class OrderAggregate { private readonly CustomerEntity _customer; private int _previousEarnedPoints; public OrderAggregate(CustomerEntity customer) { _customer = customer; } // 创建订单时记录并增加积分 public void CreateOrder(int earnedPoints) { _customer.AddLoyaltyPoint(earnedPoints); _previousEarnedPoints = earnedPoints; } // 修改订单时计算积分差额并调用调整方法 public void ModifyOrder(int newEarnedPoints) { var adjustment = newEarnedPoints - _previousEarnedPoints; _customer.AdjustLoyaltyPointForOrderModification(adjustment); _previousEarnedPoints = newEarnedPoints; } }
3. 额外的防护建议
- 给
AdjustLoyaltyPointForOrderModification添加清晰的XML注释,明确说明该方法仅用于订单修改场景,禁止其他用途。 - 编写单元测试覆盖两种场景:验证主动兑换必须触发250积分检查,订单调整则跳过该检查但保证积分不为负。
- 如果团队规模较大,可以引入
OrderLoyaltyAdjustmentService领域服务,让订单调整的积分操作必须通过该服务执行,进一步封装逻辑,减少误用风险。
内容的提问来源于stack exchange,提问作者fingers10
相关产品推荐
相关产品推荐

