第5课:应用层与领域服务
一、目标
通过本课,你将学会:
- 理解 应用服务 与 领域服务 的职责边界
- 将复杂业务逻辑(如折扣计算、库存检查)提取为独立的领域服务
- 重构
OrderLifecycleService,让其更"轻"
二、概念回顾
| 层级 | 职责 | 是否依赖领域模型 | 示例 |
|---|---|---|---|
| 应用层(Application Service) | 协调领域对象完成用例;处理事务、权限、事件等 | ✅ 是 | 下单、支付、发货 |
| 领域层(Domain Service) | 实现不适合放在实体或值对象中的业务规则 | ❌ 否 | 折扣计算、库存检查 |
| 基础设施层(Infrastructure) | 负责外部资源访问(数据库、API、MQ等) | ✅ 是 | JPA、Redis、消息总线 |
三、改造思路
当前的 OrderLifecycleService:
java
public Order createOrder() {
Order order = new Order(OrderId.newId());
order.addMenuItem(menu.findItem("Fried Rice"), 1);
order.addMenuItem(menu.findItem("Coke"), 2);
repository.save(order);
return order;
}
问题:
- 业务逻辑(选菜、折扣、校验)都在应用层;
- 随着逻辑变复杂,应用层容易臃肿;
- Menu、Repository 等依赖太多,容易造成循环依赖。
我们要做的是:
把业务逻辑拆分为 领域服务 ,让 OrderLifecycleService 只负责"编排"。
四、实现代码
新建领域服务:PricingService
负责折扣计算、订单总额调整。
java
package com.example.ordersystem.domain.order.service;
import com.example.ordersystem.domain.order.model.Money;
import com.example.ordersystem.domain.order.model.Order;
import com.example.ordersystem.domain.order.model.OrderItem;
import org.springframework.stereotype.Service;
/**
* 领域服务:计算订单价格、折扣逻辑
*/
@Service
public class PricingService {
/**
* 根据业务规则计算订单总价
* 示例规则:
* - 如果总额超过 50 元,打 9 折
* - 如果包含 "Coke",第二瓶半价
*/
public Money calculateTotal(Order order) {
double total = 0;
for (OrderItem item : order.getItems()) {
double itemTotal = item.getPrice().getAmount() * item.getQuantity();
// 特殊优惠:Coke 第二瓶半价
if (item.getName().equalsIgnoreCase("Coke") && item.getQuantity() >= 2) {
itemTotal -= item.getPrice().getAmount() * 0.5;
}
total += itemTotal;
}
// 满减折扣
if (total >= 50) {
total *= 0.9;
}
return new Money(total);
}
}
新建领域服务:InventoryService
负责库存校验(通常会依赖外部仓储系统)。
java
package com.example.ordersystem.domain.order.service;
import com.example.ordersystem.domain.menu.model.MenuItem;
import org.springframework.stereotype.Service;
/**
* 领域服务:库存检查
* (这里简单模拟逻辑,真实系统可能连接库存微服务)
*/
@Service
public class InventoryService {
public boolean checkStock(MenuItem item, int quantity) {
// 模拟:可乐最多卖 100 瓶,炒饭最多卖 50 份
if (item.getName().equalsIgnoreCase("Coke")) {
return quantity <= 100;
}
if (item.getName().equalsIgnoreCase("Fried Rice")) {
return quantity <= 50;
}
return true;
}
}
重构应用层:OrderLifecycleService
java
package com.example.ordersystem.application.order;
import com.example.ordersystem.domain.menu.model.Menu;
import com.example.ordersystem.domain.menu.model.MenuItem;
import com.example.ordersystem.domain.order.model.*;
import com.example.ordersystem.domain.order.repository.OrderRepository;
import com.example.ordersystem.domain.order.service.InventoryService;
import com.example.ordersystem.domain.order.service.PricingService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 应用服务:编排订单生命周期
* 仅协调各领域对象、服务,不承载具体业务逻辑
*/
@Service
public class OrderLifecycleService {
private final OrderRepository repository;
private final Menu menu;
private final PricingService pricingService;
private final InventoryService inventoryService;
public OrderLifecycleService(OrderRepository repository,
Menu menu,
PricingService pricingService,
InventoryService inventoryService) {
this.repository = repository;
this.menu = menu;
this.pricingService = pricingService;
this.inventoryService = inventoryService;
}
/** 创建订单 */
@Transactional
public Order createOrder() {
Order order = new Order(OrderId.newId());
MenuItem friedRice = menu.findItem("Fried Rice");
MenuItem coke = menu.findItem("Coke");
if (!inventoryService.checkStock(friedRice, 1))
throw new IllegalStateException("Fried Rice 库存不足!");
if (!inventoryService.checkStock(coke, 2))
throw new IllegalStateException("Coke 库存不足!");
order.addMenuItem(friedRice, 1);
order.addMenuItem(coke, 2);
// 计算总价(含优惠)
Money total = pricingService.calculateTotal(order);
System.out.println("订单优惠后总价:" + total.getAmount());
repository.save(order);
return order;
}
/** 支付订单 */
@Transactional
public Order payOrder(String orderId) {
Order order = repository.findById(new OrderId(orderId));
order.pay();
repository.save(order);
return order;
}
/** 确认订单 */
@Transactional
public Order confirmOrder(String orderId) {
Order order = repository.findById(new OrderId(orderId));
order.confirm();
repository.save(order);
return order;
}
}
五、运行与验证
启动后执行:
POST http://localhost:8080/orders/create
控制台输出:
订单优惠后总价:18.5
API 响应:
json
{
"id": {"value": "0ff7d..." },
"status": "CREATED",
"totalAmount": {"amount": 18.5}
}
说明:
PricingService正常计算;InventoryService校验通过;- 无循环依赖;
- 各层职责清晰。
六、总结
| 角色 | 职责 | 位置 |
|---|---|---|
OrderLifecycleService |
用例编排(事务、调用) | 应用层 |
PricingService |
折扣逻辑 | 领域层 |
InventoryService |
业务校验(库存) | 领域层 |
JpaOrderRepositoryImpl |
数据持久化 | 基础设施层 |