Skip to content
第 6 章 架构 ⏱ 10 分钟阅读

第 6 章:领域服务 ​

学习目标 ​

  • 区分应用服务、领域服务、基础设施服务
  • 学会判断什么时候该用领域服务
  • 掌握领域服务的"无状态"特性
  • 避免把领域服务当 Service 工具类滥用

一、三层服务的区别 ​

text
应用服务(Application Service)
  - 编排业务流程,事务边界
  - 调用领域服务、仓储、领域事件
  - 典型:OrderApplicationService.createOrder()

领域服务(Domain Service)
  - 跨实体的业务规则
  - 无状态,只依赖仓储
  - 典型:PricingService.calculate()
  - 命名通常是动词:transfer / calculate / merge

基础设施服务(Infrastructure Service)
  - 封装技术细节(发邮件、调支付、查 Redis)
  - 典型:EmailSender / PaymentClient
java
// 应用服务:组合多个步骤
@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

动手练习 ​

  1. 领域服务识别:在订单系统里挑一个跨实体规则,写 TransferService 或 RefundService
  2. 拆分大服务:把一个 500 行的 OrderService 拆成 OrderApplicationService + PricingService + InventoryService
  3. 事件发布:在 TransferService.transfer() 完成后发 MoneyTransferredEvent
  4. 重构 Util:把一个 StringUtils.isPhone() 工具类改写为 PhoneValidator 领域服务

下一章:第 7 章:微服务入门 →

本站基于 VitePress 构建 · 由 StackHub 团队维护