
知乎上有人提出一个问题:
领域对象讲究充血模型在理论上讲非常合理。
可是在实践过程中就会陷入“业务逻辑到底应该写在领域对象还是领域服务”的怪圈;同时看代码的人也无法知道业务逻辑到底会散落在什么地方;最可怕的是原本简单的可以放在领域对象的逻辑因为业务需求变复杂之后很可能已经超出领域对象能处理的范畴,需要转移到领域服务 与其这样纠结,为何不干脆把职责分离了,领域服务承载所有业务逻辑,领域对象作为贫血模型承载数据结构的职责呢?
领域对象 + 领域服务 = 数据 + 行为 而不是强求领域对象既包含数据又包含行为
这是一个非常好的问题,它触及了DDD实践中最核心的困惑点。这个观察非常准确:理论上充血模型很合理,但实践中却容易陷入“业务逻辑到底放在哪”的泥潭,导致代码散乱、难以维护。
我的答案是:领域对象必须“充血”,但我们需要重新理解“充血”的真正含义。它不应是传统意义上将全部行为塞进实体内的“行为充血”,而应是构建一个富含领域语义、可自由导航的“结构充血”。
传统DDD实践中的一个常见误区,是试图让聚合根成为全能的“行为上帝”。例如,将一个复杂的placeOrder(下单)流程的所有逻辑——检查库存、验证优惠券、计算价格、创建订单——全部封装进Order对象的placeOrder()方法中。
这会导致你提到的所有问题:
Order 对象变成一个巨大的“上帝类”,难以理解和测试。其根本原因,是我们混淆了领域模型中的两个正交关注点:
一种更先进的实践是进行关注点分离:
NopTaskFlow)或领域服务来协调。这才是“充血模型”的真正价值所在。想象一下,你的领域模型本身构成了一种“语言”:
Order、Customer、totalAmount、isVIP())。例如,order.customer.creditLimit 或 order.canBeCancelled() 就是一个符合文法的、有业务意义的领域表达式。
在这种范式下,编程变成了“只针对聚合根编程”。你的业务逻辑代码中,只出现这些纯粹的领域表达式,而完全看不到 orderRepository.findById(...) 或 customerService.getCreditLimit(...) 这种技术性代码。聚合根为你构建了一个稳定的、与技术无关的“信息宇宙”,你只需在其中自由导航。
这里有一个至关重要的区别:聚合根不能是简单的DTO。DTO是数据的被动载体,而聚合根是主动的信息空间。
Order 应该有 getCustomer()、getItems()、getShippingAddress() 等方法,无论调用方是否需要这些信息。order.getCustomer().getName() 时,系统才真正去加载客户信息。这就是“延迟加载”的精髓。形式上的聚合 ≠ 数据存取时机的聚合。我们在设计时按照领域边界进行逻辑聚合,但在运行时按需加载数据。这完美解决了性能顾虑——你不会因为设计了一个丰富的领域模型就不得不加载所有数据。
这正是GraphQL与聚合根天生契合的原因。GraphQL的 @selection 机制与声明式聚合根形成了完美的对偶关系:
正如NopGraphQL引擎的做法,我们完全可以在REST接口中引入 @selection 参数,获得同样的能力:
# 传统REST - 返回所有字段
GET /orders/123
# 增强REST - 按需返回
GET /orders/123?@selection=id,status,customer{name,email}这种设计让聚合根的丰富领域表达能力与接口的精确数据需求达成了完美平衡。你既可以设计出完整表达业务概念的领域模型,又不用担心性能问题。
那么,复杂的业务逻辑(行为)去哪了?它们被提取到流程编排引擎(如NopTaskFlow)中。
以“订单打折”为例,在传统充血模型中,你可能会在Order中写一个庞大的calculateDiscount方法,里面充满if-else。而在新范式下:
# 在流程引擎中定义打折规则(行为)
steps:
- type: xpl
name: book_discount_1
when: "orderBo.originalPrice < 100" # 这里访问聚合根的信息
source: |
orderBo.order.setRealPrice(orderBo.originalPrice);
- type: xpl
name: book_discount_4
when: "orderBo.originalPrice >= 300"
source: |
orderBo.order.setRealPrice(orderBo.getOriginalPrice() - 100);在这个例子中:
Order) 提供了 getOriginalPrice() 和 setRealPrice() 方法。它充的是“结构”和“基础行为”的血,负责表达“订单有原价和实付价”这个领域事实。Order聚合根,只需在PlaceOrderTaskFlow中插入一个新的步骤。系统的复杂性与核心领域模型的复杂性解耦,演化能力得到质的提升。所以,回到你的问题:与其退回到贫血模型,放弃面向对象的所有好处,不如进行一次范式升级。
这种“声明式聚合根(结构空间) + 流程引擎(行为动力学) + GraphQL(按需消费)”的架构,既保留了面向对象封装和领域表达力的优势,又获得了流程化编排的灵活性、可视性和可维护性,同时还彻底解决了性能顾虑。它才是DDD在现代复杂业务系统中最有生命力的实践方式。
基于可逆计算理论设计的低代码平台NopPlatform已开源:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。