DDD-012:领域服务(Domain Service)12.1 领域服务的定义12.1.1 什么是领域服务?【原理】领域服务(Domain Service)是领域模型中的一种特殊概念,用于封装那些不自然属于任何实体或值对象的业务逻辑。领域服务代表领域中的业务操作或流程,而非事物。领域服务的核心特征:无状态:不持有业务数据,只有行为领域逻辑:包含真实的业务规则命名规范:通常以动词或动词短语命名(如 TransferService、TaxCalculator)接口在领域层:定义在领域层,体现业务契约12.1.2 领域服务与实体/值对象的区别【对比分析】特征实体(Entity)值对象(Value Object)领域服务(Domain Service)标识有唯一ID无标识(由属性确定)无标识状态持有状态数据持有不可变数据无状态行为有行为方法有行为方法只有行为生命周期创建、修改、销毁不可变无生命周期概念命名名词(Order、Customer)名词(Money、Address)动词(Transfer、Calculate)适用场景需要跟踪的对象描述性概念跨对象或无归属的操作12.1.3 领域服务的命名规范【代码示例】// ✅ 正确命名:动词或动词短语publicinterfaceTransferService{}// 转账服务publicinterfaceTaxCalculator{}// 税费计算器publicinterfacePricingService{}// 定价服务publicinterfaceAuthorizationService{}// 授权服务publicinterfaceIdentityGenerator{}// ID生成器// ❌ 错误命名:名词(应该用实体或值对象)publicinterfaceOrderService{}// 太宽泛,可能是应用服务publicinterfaceUserManager{}// 应该是User实体或UserRepository12.2 领域服务的使用场景12.2.1 场景一:跨聚合的业务逻辑【历史架构问题】// ❌ 传统做法:业务逻辑分散或放在错误的位置@ServicepublicclassAccountService{@AutowiredprivateAccountDaoaccountDao;@Transactionalpublicvoidtransfer(LongfromId,LongtoId,BigDecimalamount){// 直接操作数据Accountfrom=accountDao.findById(fromId);Accountto=accountDao.findById(toId);from.setBalance(from.getBalance().subtract(amount));to.setBalance(to.getBalance().add(amount));accountDao.update(from);accountDao.update(to);// 问题:// 1. Account实体的业务规则被绕过// 2. 转账逻辑不属于单个Account实体// 3. 一致性难以保证}}【DDD 如何解决】// ✅ DDD领域服务:协调多个聚合publicinterfaceTransferService{/** * 执行转账 * 涉及两个Account聚合,因此放在领域服务 */voidtransfer(Accountfrom,Accountto,Moneyamount);}@ServicepublicclassTransferServiceImplimplementsTransferService{@Overridepublicvoidtransfer(Accountfrom,Accountto,Moneyamount){// 验证参数Objects.requireNonNull(from,"转出账户不能为空");Objects.requireNonNull(to,"转入账户不能为空");Objects.requireNonNull(amount,"转账金额不能为空");// 执行业务规则(调用实体的行为)from.withdraw(amount);// Account实体负责自己的取款逻辑to.deposit(amount);// Account实体负责自己的存款逻辑// 领域服务只负责协调,不直接修改账户状态}}// Account实体保持自己的业务规则publicclassAccountextendsAggregateRootAccountId{privateMoneybalance;publicvoidwithdraw(Moneyamount){if(amount.isNegativeOrZero()){thrownewIllegalArgumentException("取款金额必须大于0");}if(balance.isLessThan(amount)){thrownewInsufficientBalanceException("余额不足");}this.balance=this.balance.subtract(amount);registerEvent(newMoneyWithdrawnEvent(this.id,amount));}publicvoiddeposit(Moneyamount){if(amount.isNegativeOrZero()){thrownewIllegalArgumentException("存款金额必须大于0");}this.balance=this.balance.add(amount);registerEvent(newMoneyDepositedEvent(this.id,amount));}}12.2.2 场景二:不自然属于任何实体的逻辑【代码示例】// 场景:计算订单税费// 税费计算涉及订单总金额、收货地址、商品类型等多个因素// 但这个逻辑不自然属于Order,也不属于Address// ✅ 领域服务:税费计算publicinterfaceTaxCalculator{/** * 计算订单税费 */Moneycalculate(Orderorder);/** * 根据地址和金额计算税费 */Moneycalculate(ShippingAddressaddress,Moneyamount);}@ServicepublicclassChinaTaxCalculatorimplementsTaxCalculator{@OverridepublicMoneycalculate(Orderorder){returncalculate(order.getShippingAddress(),order.getTotalAmount());}@OverridepublicMoneycalculate(ShippingAddressaddress,Moneyamount){// 根据地址确定税率BigDecimaltaxRate=determineTaxRate(address);// 计算税费Moneytax=amount.multiply(taxRate);// 应用最小税费规则if(tax.isLessThan(Money.of("1.00"))){returnMoney.of("1.00");}returntax;}privateBigDecimaldetermineTaxRate(ShippingAddressaddress){// 根据省份确定税率// 实际项目中可能需要查询税率表returnswitch(address.getProvince()){case"上海","北京","广东"-newBigDecimal("0.13");case"西藏","新疆"-newBigDecimal("0.09");default-newBigDecimal("0.11");};}}12.2.3 场景三:需要多个领域对象协作【代码示例】// 场景:价格计算// 最终价格 = 原价 × 会员折扣 × 活动折扣 - 优惠券// ✅ 领域服务:定价服务publicinterfacePricingService{/** * 计算商品最终价格 */MoneycalculateFinalPrice(Productproduct,Customercustomer,Promotionpromotion,Couponcoupon);/** * 计算订单总金额(含优惠) */MoneycalculateOrderTotal(Orderorder,Customercustomer);}@ServicepublicclassPricingServiceImplimplementsPricingService{@OverridepublicMoneycalculateFinalPrice(Productproduct,Customercustomer,Promotionpromotion,Couponcoupon){MoneybasePrice=product.getPrice();// 会员折扣MoneyafterMemberDiscount=applyMemberDiscount(basePrice,customer);// 活动折扣MoneyafterPromotion=applyPromotion(afterMemberDiscount,promotion);// 优惠券MoneyfinalPrice=applyCoupon(afterPromotion,coupon);// 保证最低价格if(finalPrice.isLessThan(product.getCostPrice())){finalPrice=product.getCostPrice();}returnfinalPrice;}privateMoneyapplyMemberDiscount(Moneyprice,Customercustomer){if(customer==null)returnprice;returnprice.multiply(BigDecimal.ONE.subtract(customer.getDiscountRate()));}privateMoneyapplyPromotion(Moneyprice,Promotionpromotion){if(promotion==null||!promotion.isActive())returnprice;returnpromotion.apply(price);}privateMoneyapplyCoupon(Moneyprice,Couponcoupon){if(coupon==null||coupon.isUsed())returnprice;returncoupon.apply(price);}}12.2.4 场景四:纯计算或转换场景【代码示例】// 场景1:ID生成(纯计算,无状态)publicinterfaceIdentityGenerator{OrderIdgenerateOrderId();OrderItemIdgenerateOrderItemId();StringgenerateTransactionNo();}@ServicepublicclassUuidIdentityGeneratorimplementsIdentityGenerator{@OverridepublicOrderIdgenerateOrderId(){returnOrderId.generate();}@OverridepublicOrderItemIdgenerateOrderItemId(){returnOrderItemId.generate();}@OverridepublicStringgenerateTransactionNo(){// 业务流水号格式:日期 + 随机数Stringdate=LocalDateTime.
DDD-012:领域服务(Domain Service)
DDD-012:领域服务(Domain Service)12.1 领域服务的定义12.1.1 什么是领域服务?【原理】领域服务(Domain Service)是领域模型中的一种特殊概念,用于封装那些不自然属于任何实体或值对象的业务逻辑。领域服务代表领域中的业务操作或流程,而非事物。领域服务的核心特征:无状态:不持有业务数据,只有行为领域逻辑:包含真实的业务规则命名规范:通常以动词或动词短语命名(如 TransferService、TaxCalculator)接口在领域层:定义在领域层,体现业务契约12.1.2 领域服务与实体/值对象的区别【对比分析】特征实体(Entity)值对象(Value Object)领域服务(Domain Service)标识有唯一ID无标识(由属性确定)无标识状态持有状态数据持有不可变数据无状态行为有行为方法有行为方法只有行为生命周期创建、修改、销毁不可变无生命周期概念命名名词(Order、Customer)名词(Money、Address)动词(Transfer、Calculate)适用场景需要跟踪的对象描述性概念跨对象或无归属的操作12.1.3 领域服务的命名规范【代码示例】// ✅ 正确命名:动词或动词短语publicinterfaceTransferService{}// 转账服务publicinterfaceTaxCalculator{}// 税费计算器publicinterfacePricingService{}// 定价服务publicinterfaceAuthorizationService{}// 授权服务publicinterfaceIdentityGenerator{}// ID生成器// ❌ 错误命名:名词(应该用实体或值对象)publicinterfaceOrderService{}// 太宽泛,可能是应用服务publicinterfaceUserManager{}// 应该是User实体或UserRepository12.2 领域服务的使用场景12.2.1 场景一:跨聚合的业务逻辑【历史架构问题】// ❌ 传统做法:业务逻辑分散或放在错误的位置@ServicepublicclassAccountService{@AutowiredprivateAccountDaoaccountDao;@Transactionalpublicvoidtransfer(LongfromId,LongtoId,BigDecimalamount){// 直接操作数据Accountfrom=accountDao.findById(fromId);Accountto=accountDao.findById(toId);from.setBalance(from.getBalance().subtract(amount));to.setBalance(to.getBalance().add(amount));accountDao.update(from);accountDao.update(to);// 问题:// 1. Account实体的业务规则被绕过// 2. 转账逻辑不属于单个Account实体// 3. 一致性难以保证}}【DDD 如何解决】// ✅ DDD领域服务:协调多个聚合publicinterfaceTransferService{/** * 执行转账 * 涉及两个Account聚合,因此放在领域服务 */voidtransfer(Accountfrom,Accountto,Moneyamount);}@ServicepublicclassTransferServiceImplimplementsTransferService{@Overridepublicvoidtransfer(Accountfrom,Accountto,Moneyamount){// 验证参数Objects.requireNonNull(from,"转出账户不能为空");Objects.requireNonNull(to,"转入账户不能为空");Objects.requireNonNull(amount,"转账金额不能为空");// 执行业务规则(调用实体的行为)from.withdraw(amount);// Account实体负责自己的取款逻辑to.deposit(amount);// Account实体负责自己的存款逻辑// 领域服务只负责协调,不直接修改账户状态}}// Account实体保持自己的业务规则publicclassAccountextendsAggregateRootAccountId{privateMoneybalance;publicvoidwithdraw(Moneyamount){if(amount.isNegativeOrZero()){thrownewIllegalArgumentException("取款金额必须大于0");}if(balance.isLessThan(amount)){thrownewInsufficientBalanceException("余额不足");}this.balance=this.balance.subtract(amount);registerEvent(newMoneyWithdrawnEvent(this.id,amount));}publicvoiddeposit(Moneyamount){if(amount.isNegativeOrZero()){thrownewIllegalArgumentException("存款金额必须大于0");}this.balance=this.balance.add(amount);registerEvent(newMoneyDepositedEvent(this.id,amount));}}12.2.2 场景二:不自然属于任何实体的逻辑【代码示例】// 场景:计算订单税费// 税费计算涉及订单总金额、收货地址、商品类型等多个因素// 但这个逻辑不自然属于Order,也不属于Address// ✅ 领域服务:税费计算publicinterfaceTaxCalculator{/** * 计算订单税费 */Moneycalculate(Orderorder);/** * 根据地址和金额计算税费 */Moneycalculate(ShippingAddressaddress,Moneyamount);}@ServicepublicclassChinaTaxCalculatorimplementsTaxCalculator{@OverridepublicMoneycalculate(Orderorder){returncalculate(order.getShippingAddress(),order.getTotalAmount());}@OverridepublicMoneycalculate(ShippingAddressaddress,Moneyamount){// 根据地址确定税率BigDecimaltaxRate=determineTaxRate(address);// 计算税费Moneytax=amount.multiply(taxRate);// 应用最小税费规则if(tax.isLessThan(Money.of("1.00"))){returnMoney.of("1.00");}returntax;}privateBigDecimaldetermineTaxRate(ShippingAddressaddress){// 根据省份确定税率// 实际项目中可能需要查询税率表returnswitch(address.getProvince()){case"上海","北京","广东"-newBigDecimal("0.13");case"西藏","新疆"-newBigDecimal("0.09");default-newBigDecimal("0.11");};}}12.2.3 场景三:需要多个领域对象协作【代码示例】// 场景:价格计算// 最终价格 = 原价 × 会员折扣 × 活动折扣 - 优惠券// ✅ 领域服务:定价服务publicinterfacePricingService{/** * 计算商品最终价格 */MoneycalculateFinalPrice(Productproduct,Customercustomer,Promotionpromotion,Couponcoupon);/** * 计算订单总金额(含优惠) */MoneycalculateOrderTotal(Orderorder,Customercustomer);}@ServicepublicclassPricingServiceImplimplementsPricingService{@OverridepublicMoneycalculateFinalPrice(Productproduct,Customercustomer,Promotionpromotion,Couponcoupon){MoneybasePrice=product.getPrice();// 会员折扣MoneyafterMemberDiscount=applyMemberDiscount(basePrice,customer);// 活动折扣MoneyafterPromotion=applyPromotion(afterMemberDiscount,promotion);// 优惠券MoneyfinalPrice=applyCoupon(afterPromotion,coupon);// 保证最低价格if(finalPrice.isLessThan(product.getCostPrice())){finalPrice=product.getCostPrice();}returnfinalPrice;}privateMoneyapplyMemberDiscount(Moneyprice,Customercustomer){if(customer==null)returnprice;returnprice.multiply(BigDecimal.ONE.subtract(customer.getDiscountRate()));}privateMoneyapplyPromotion(Moneyprice,Promotionpromotion){if(promotion==null||!promotion.isActive())returnprice;returnpromotion.apply(price);}privateMoneyapplyCoupon(Moneyprice,Couponcoupon){if(coupon==null||coupon.isUsed())returnprice;returncoupon.apply(price);}}12.2.4 场景四:纯计算或转换场景【代码示例】// 场景1:ID生成(纯计算,无状态)publicinterfaceIdentityGenerator{OrderIdgenerateOrderId();OrderItemIdgenerateOrderItemId();StringgenerateTransactionNo();}@ServicepublicclassUuidIdentityGeneratorimplementsIdentityGenerator{@OverridepublicOrderIdgenerateOrderId(){returnOrderId.generate();}@OverridepublicOrderItemIdgenerateOrderItemId(){returnOrderItemId.generate();}@OverridepublicStringgenerateTransactionNo(){// 业务流水号格式:日期 + 随机数Stringdate=LocalDateTime.