领域服务是业务逻辑分配失衡时的合理出口,用于处理跨聚合根协调、纯计算型逻辑及对象创建前转换三类场景,其本质在于承载业务语义而非技术功能。

领域服务不是“补丁”,而是业务逻辑分配失衡时的合理出口——当变量和行为无法自然归属到某个实体或值对象身上时,它才该出现。
哪些情况必须用领域服务
不是所有跨对象操作都需要领域服务,但以下三类场景绕不开它:
- 涉及两个及以上聚合根的协调动作,比如“用户下单”需同时校验库存、计算运费、冻结优惠券,这些聚合各自独立,不能让 Order 直接调用 Inventory 或 Coupon 的内部方法
- 纯计算型逻辑,不改变任何对象状态,例如根据订单金额、地区、会员等级综合得出“应享折扣率”,这类规则无归属实体,也不依赖生命周期
- 对象创建前的转换与组装,比如从 JSON 请求参数中解析并验证出一个合法的 ShippingAddress 值对象,或把多个原始字段组合成一个完整的 Order 创建上下文
领域服务和普通工具类的本质区别
关键不在“有没有方法”,而在“是否承载业务语义”:
- 领域服务的方法名必须是业务语言,如 calculateFinalPriceForOrder()、validateInventoryAvailabilityBeforeCheckout(),而不是 StringUtils.isEmpty() 或 DateUtils.addDays()
- 它只依赖领域模型(实体、值对象、其他领域服务),绝不引用 DAO、Repository、Spring Bean、日志框架或 HTTP 客户端
- 它是无状态的,不持有任何业务数据,每次调用只靠输入参数驱动,结果可预测、可测试
变量与逻辑怎么分给对象 vs 领域服务
判断依据很简单:看这个逻辑是否天然属于某个业务概念的“本能”:
- 如果逻辑围绕一个核心业务名词展开,且操作的是它的自身状态——比如 “订单取消”要检查发货状态、触发退款、更新状态,那就属于 Order.cancel(),不是领域服务的事
- 如果逻辑需要“拉通多个业务名词”才能完成,且没有一个名词明显是主语——比如 “跨店合并购物车”涉及用户、多个店铺、商品库存、促销规则,那它就该由 CartMergingService 承担
- 地址格式校验、金额四舍五入、时间区间重叠判断——这些是值对象自己的事,应封装进 Address、Money、TimeRange 类里,不是领域服务的职责
命名与组织建议
避免模糊词,强化业务角色感:
- 用 …Service 后缀,如 PricingService、EligibilityService,不叫 Util、Helper、Manager
- 接口与实现都放在 domain 包下,如 com.example.order.domain.service,不混入 infrastructure 或 application 层
- 一个领域服务只聚焦一个业务能力域,不要堆砌十几个方法;方法粒度适中,一个方法完成一个完整业务意图,而非多个子步骤










