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 数据持久化 基础设施层
相关推荐
better_liang2 天前
每日Java面试场景题知识点之-DDD领域驱动设计
java·ddd·实体·领域驱动设计·架构设计·聚合根·企业级开发
ByteX3 天前
DDD学习第3课设计聚合与聚合根
ddd
怀川4 天前
开源 NamBlog:一个博客外壳下的体验编译器
docker·ai·.net·博客·ddd·graphql·mcp
没有bug.的程序员6 天前
业务中台设计原则:从理念到落地的系统性工程
ddd·微服务架构·稳定性·中台设计·业务中台·能力复用·扩展点设计
职业码农NO.113 天前
系统架构设计中的 15 个关键取舍
设计模式·架构·系统架构·ddd·架构师·设计规范·领域驱动
彷徨的蜗牛23 天前
六边形架构补充 - 第五章 - DDD领域模型
架构·领域模型·ddd
rolt1 个月前
[漫画]《软件方法》微服务的遮羞布
微服务·ddd·领域驱动设计
彷徨的蜗牛1 个月前
六边形架构代码设计及实现 - 第四章 - DDD领域模型
架构·领域模型·ddd
彷徨的蜗牛1 个月前
六边形架构的调用流程 - 第三章 - DDD领域模型
架构·领域模型·ddd