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 数据持久化 基础设施层
相关推荐
Coder_Boy_3 天前
基于SpringAI的在线考试系统-数据库设计核心业务方案
java·数据库·spring boot·ddd·tdd
黑棠会长3 天前
ABP框架04.复杂业务关系实现(DDD实战)
数据库·c#·.net·ddd·abp
小庄4 天前
如何正确的 DDD
微服务·ddd·洋葱架构
Coder_Boy_6 天前
基于SpringAI的在线考试系统-智能考试系统-学习分析模块
java·开发语言·数据库·spring boot·ddd·tdd
Coder_Boy_7 天前
基于SpringAI的在线考试系统-阅卷评分与错题管理模块回归测试逻辑梳理文档
java·spring boot·系统架构·ddd·tdd·全栈开发
小庄7 天前
AI时代的领域驱动设计:DAD
ddd·洋葱架构
Coder_Boy_8 天前
基于SpringAI的在线考试系统-考试管理功能布局+交互优化方案
java·数据库·人工智能·spring boot·交互·ddd·tdd
七夜zippoe8 天前
微服务架构演进实战 从单体到微服务的拆分原则与DDD入门
java·spring cloud·微服务·架构·ddd·绞杀者策略
Coder_Boy_9 天前
基于SpringAI的在线考试系统-0到1全流程研发:DDD、TDD与CICD协同实践
java·人工智能·spring boot·架构·ddd·tdd
rolt9 天前
[pdf]《软件方法》全流程引领AI-电子书共435页202601更新
产品经理·ddd·架构师·uml·领域驱动设计