第 6 章:领域服务
学习目标
- 区分应用服务、领域服务、基础设施服务
- 学会判断什么时候该用领域服务
- 掌握领域服务的"无状态"特性
- 避免把领域服务当 Service 工具类滥用
一、三层服务的区别
text
应用服务(Application Service)
- 编排业务流程,事务边界
- 调用领域服务、仓储、领域事件
- 典型:OrderApplicationService.createOrder()
领域服务(Domain Service)
- 跨实体的业务规则
- 无状态,只依赖仓储
- 典型:PricingService.calculate()
- 命名通常是动词:transfer / calculate / merge
基础设施服务(Infrastructure Service)
- 封装技术细节(发邮件、调支付、查 Redis)
- 典型:EmailSender / PaymentClientjava
// 应用服务:组合多个步骤
@Service
public class OrderApplicationService {
@Resource private OrderRepository orderRepository;
@Resource private PricingService pricingService; // 领域服务
@Resource private InventoryService inventoryService;
@Transactional
public OrderId createOrder(CreateOrderCmd cmd) {
// 1. 价格计算(领域规则)
Money total = pricingService.calculate(cmd.items(), cmd.coupon(), cmd.member());
// 2. 库存预占(领域规则)
inventoryService.reserve(cmd.items());
// 3. 创建订单
Order order = Order.create(cmd.buyerId(), cmd.address(), total, cmd.items());
orderRepository.save(order);
return order.id();
}
}⚠️ 坑 1:应用服务里写
if (member.level == GOLD) price = price * 0.8,这是业务规则不是编排。移去领域服务。
二、领域服务的典型场景
java
// 场景 1:转账(跨两个聚合)
@Service
public class TransferService {
@Resource private AccountRepository accountRepository;
@Transactional
public void transfer(AccountId fromId, AccountId toId, Money amount) {
Account from = accountRepository.findById(fromId);
Account to = accountRepository.findById(toId);
from.debit(amount); // 实体方法
to.credit(amount); // 实体方法
accountRepository.save(from);
accountRepository.save(to);
}
}
// 场景 2:价格计算(多因子)
@Service
public class PricingService {
public Money calculate(List<OrderItem> items, Coupon coupon, Member member) {
Money subtotal = items.stream()
.map(OrderItem::subtotal)
.reduce(Money.zero("CNY"), Money::add);
Money memberDiscount = member.level().discount(subtotal);
Money after = subtotal.subtract(memberDiscount);
if (coupon != null) {
after = coupon.apply(after);
}
return after;
}
}
// 场景 3:用户名校验(规则跨实体)
@Service
public class IdentityService {
public boolean isUnique(String username) {
return !userRepository.existsByUsername(username);
}
}三、领域服务的设计原则
java
// ✅ 原则 1:无状态
@Service
public class PricingService {
public Money calculate(...) {
// 不保存任何字段,不缓存
}
}
// ✅ 原则 2:只依赖仓储 + 其他领域服务
@Service
public class PricingService {
@Resource private CouponRepository couponRepository;
@Resource private MemberRepository memberRepository;
}
// ✅ 原则 3:命名是"动词+泛化对象"
public class TransferService {} // Transfer
public class PricingService {} // Pricing
public class ReservationService {} // Reservation
// ❌ 错误命名:Util / Helper / Manager
public class PriceUtil {} // 这不是领域服务
public class OrderHelper {} // 这是工具类⚠️ 坑 2:领域服务类膨胀到 1000+ 行,啥都干,变成"业务工具箱"。按业务能力拆分,
PricingService、DiscountService、TaxService各自一份。
四、领域服务 vs 实体方法
判断标准:规则只涉及一个实体,放进实体;涉及多个实体,放进领域服务。
java
// 单实体规则 → 实体方法
public class Order {
public void cancel() {
if (status == OrderStatus.PAID) {
throw new CannotCancelException();
}
this.status = OrderStatus.CANCELLED;
}
}
// 跨实体规则 → 领域服务
@Service
public class OrderMergeService {
public Order merge(Order a, Order b) {
// 把两个订单合并成一个,涉及多 Order
}
}⚠️ 坑 3:
OrderService.cancel(long orderId)看似领域服务,实际可以移到Order.cancel()实体方法,调用方OrderApplicationService编排即可。
五、领域事件配合
领域服务在状态变更后发布事件:
java
@Service
public class TransferService {
@Resource private AccountRepository accountRepository;
@Resource private ApplicationEventPublisher publisher;
@Transactional
public void transfer(AccountId fromId, AccountId toId, Money amount) {
Account from = accountRepository.findById(fromId);
Account to = accountRepository.findById(toId);
from.debit(amount);
to.credit(amount);
accountRepository.save(from);
accountRepository.save(to);
// 通知其他系统
publisher.publishEvent(new MoneyTransferredEvent(fromId, toId, amount));
}
}六、领域服务 vs 工具类
java
// ❌ 工具类:无业务语义,纯静态
public class MoneyUtils {
public static BigDecimal add(BigDecimal a, BigDecimal b) {
return a.add(b);
}
}
// ✅ 领域服务:有业务语义
@Service
public class PricingService {
public Money calculate(List<OrderItem> items, Coupon coupon) {
// 业务规则:不同等级会员不同折扣
}
}工具类放 infrastructure/util,领域服务放 domain/service。业务规则只能存在于领域层。
本章小结
| 类型 | 职责 | 例子 |
|---|---|---|
| 应用服务 | 编排、事务边界 | OrderApplicationService |
| 领域服务 | 跨实体业务规则 | PricingService |
| 基础设施服务 | 调用外部技术 | EmailSender、JpaRepository |
动手练习
- 领域服务识别:在订单系统里挑一个跨实体规则,写
TransferService或RefundService - 拆分大服务:把一个 500 行的
OrderService拆成OrderApplicationService+PricingService+InventoryService - 事件发布:在
TransferService.transfer()完成后发MoneyTransferredEvent - 重构 Util:把一个
StringUtils.isPhone()工具类改写为PhoneValidator领域服务
下一章:第 7 章:微服务入门 →