DDD学习第5课应用层与领域服务

第5课:应用层与领域服务


一、目标

通过本课,你将学会:

  1. 理解 应用服务领域服务 的职责边界
  2. 将复杂业务逻辑(如折扣计算、库存检查)提取为独立的领域服务
  3. 重构 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 数据持久化 基础设施层
相关推荐
YDS82917 天前
大营销平台 —— 第二阶段应用接口实现
java·spring boot·ddd
YDS8291 个月前
大营销平台 —— 细节优化和前端对接
java·springboot·ddd
YDS8291 个月前
大营销平台 —— 架构解析和抽奖流程串联
java·springboot·ddd
程序猿秃头之路1 个月前
DDD 系列:DTO、VO、DO、Entity 怎么区分
java·ddd·领域驱动设计
花生了什么事o1 个月前
DDD 分层架构:六层分层架构
java·架构·ddd
YDS8291 个月前
大营销平台 —— 活动SKU库存扣减业务及其一致性处理
java·spring boot·redis·rabbitmq·ddd
Sam_Deep_Thinking2 个月前
DDD里的领域服务,到底什么时候该用?
程序员·架构·系统架构·ddd
YDS8292 个月前
大营销平台 —— 第二阶段初始化以及DB路由中间件的使用及基本原理
java·springboot·ddd
canonical-entropy2 个月前
下一代低代码渲染框架 nop-chaos-flux 的设计原则
低代码·ddd·函数式编程·可逆计算·nop平台
程序猿秃头之路2 个月前
DDD 系列:聚合和聚合根详解
数据库·oracle·ddd·领域驱动设计