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

DDD中带副作用的业务逻辑应放在何处?以优惠码适用性校验为例

DDD 优惠码校验逻辑的合理设计方案

核心设计思路

所有和优惠码相关的业务判断逻辑全部内聚在DiscountCode聚合根内部,领域服务仅负责外部依赖调用和流程编排,既避免贫血模型,也不会让逻辑散落各处。

1. 保留原有聚合内无副作用校验

原DiscountCode中的isApplicableToOrder方法不需要改动,继续负责规则1-3的纯内存校验,同时新增规则4的判断逻辑,外部仅需传入查询结果作为参数:

class DiscountCode(
    val id: DiscountCodeId,
    val hasAlreadyBeenUsed: Boolean,
    val startTime: LocalDateTime,
    val endTime: LocalDateTime
) {
    // 规则1-3校验:纯函数,无IO副作用
    fun isApplicableToOrder(order: Order): Boolean {
        return 
            startTime.isBefore(now) && endTime.isAfter(now)
            && order.items.none { it.canNotBeDiscounted }
            && !hasAlreadyBeenUsed
    }

    // 规则4校验逻辑:仍然内聚在优惠码聚合中,不包含查询操作
    fun isEligibleForCustomer(customerId: CustomerId, hasValidBinding: Boolean): Boolean {
        // 后续规则4的扩展逻辑(比如特殊用户白名单、黑名单判断等)都在这里迭代,不用修改上层服务
        return hasValidBinding
    }
}

2. 上层领域服务做流程编排

新增仅负责流程调度的领域服务,处理跨聚合的数据库查询,不包含任何业务判断逻辑:

class DiscountValidationService(
    private val discountCodeRepo: DiscountCodeRepository,
    private val discountBindingRepo: DiscountCodeCustomerBindingRepository
) {
    fun canApplyDiscount(codeId: DiscountCodeId, order: Order): Boolean {
        val discountCode = discountCodeRepo.getById(codeId) ?: return false
        // 先执行聚合内的规则1-3校验,失败直接返回
        if (!discountCode.isApplicableToOrder(order)) return false
        // 执行跨聚合关联查询,拿到规则4需要的参数
        val hasValidBinding = discountBindingRepo.exists(codeId, order.customerId)
        // 交给聚合完成规则4校验
        return discountCode.isEligibleForCustomer(order.customerId, hasValidBinding)
    }
}

设计合理性说明

  • 所有优惠码相关的业务规则全部内聚在DiscountCode类中,后续规则调整只需要修改聚合类,不需要改动上层服务,不会出现逻辑散落的问题
  • 聚合类的所有方法都是纯函数,不需要依赖数据库就能做单元测试,可测性极强
  • 领域服务仅负责IO操作和流程编排,没有业务逻辑,符合单一职责原则
  • 完全避免了大列表嵌入聚合的性能问题,关联查询的开销可控

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 16:06:01