Spring Boot + DDD电商项目架构设计详解

2026-03-31
4
-
- 分钟
|

Spring Boot + DDD电商项目架构设计详解

项目概述

本文介绍一个基于Spring Boot + DDD(领域驱动设计)的电商项目架构设计。该项目主要实现了微信公众号登录、下单业务与支付宝沙箱支付功能,采用六边形架构与DDD分层设计。

1. 库表设计

1.1 实际库表

pay_order - 支付订单表

  • id: 自增ID
  • user_id: 用户ID
  • product_id: 下单产品id
  • product_name: 下单产品名称
  • order_id: 订单id
  • order_time: 下单时间
  • total_amount: 总金额
  • status: 订单状态
  • pay_url: 支付信息
  • pay_time: 支付时间
  • create_time: 订单创建时间
  • update_time: 订单更新时间

1.2 潜在库表

注:因仅用于教学DDD,这部分库表并未实际存在,但在实际业务中应写入数据库。

user - 用户表(登录业务需要) 🔐

当前代码中使用硬编码方式:

// WeixinTemplateMessageVO.java - 默认测试用户
private String touser = "oUn5e3K_F2tv6v5Avxo0_peUksLQ";

// WeixinLoginServiceImpl.java - openid 存储在 Guava Cache
@Resource
private Cache<String, String> openidToken; // ❌ 内存存储,重启丢失

@Override
public String checkLogin(String ticket) {
    return openidToken.getIfPresent(ticket); // ❌ 无持久化
}

@Override
public void saveLoginState(String ticket, String openid) throws IOException {
    openidToken.put(ticket, openid); // ❌ 无数据库存储
}

user_session - 用户会话表(登录状态需要)🎫

当前硬编码代码:

// WeixinLoginServiceImpl.java
@Resource
private Cache<String, String> weixinAccessToken; // ❌ 内存缓存 access_token
@Resource
private Cache<String, String> openidToken; // ❌ 内存缓存登录状态

// 场景值写死
.scene(WeixinQrCodeReq.ActionInfo.Scene.builder()
    .scene_id(100601) // ❌ 硬编码场景值
    .build())

product - 商品表(下单业务需要)🛍️

当前硬编码代码:

// ProductRPC.java
@Service
public class ProductRPC {
    public ProductVO queryProductByProductId(String productId){
        ProductVO productVO = new ProductVO();
        productVO.setProductId(productId);
        productVO.setProductName("测试商品"); // ❌ 硬编码
        productVO.setProductDesc("这是一个测试商品"); // ❌ 硬编码
        productVO.setPrice(new BigDecimal("1.68")); // ❌ 硬编码
        return productVO;
    }
}

2. 核心业务流程

2.1 用户下单支付流程

  1. 用户通过前端调用 /api/v1/alipay/create_pay_order 创建支付订单
  2. OrderService 接收请求,调用支付宝 SDK 生成支付页面 URL
  3. 订单信息保存到数据库(状态:待支付)
  4. 返回支付 URL 给用户,用户完成支付
  5. 支付宝回调 /api/v1/alipay/alipay_notify_url
  6. 验证签名后更新订单状态为支付成功
  7. 通过 Guava EventBus 发布支付成功事件

2.2 微信扫码登录流程

  1. 用户扫描微信公众号二维码
  2. 微信推送扫码事件到 /api/v1/weixin/portal/receive
  3. WeixinLoginService 保存登录状态到 Guava Cache
  4. 发送微信模板消息通知用户登录成功
  5. 前端轮询检查登录状态

2.3 定时任务补偿流程

  • 超时关单任务(每 10 分钟):关闭超过 30 分钟未支付的订单
  • 支付补偿任务(每 3 秒):主动查询支付宝,处理未接收到的支付回调

3. DDD 分层架构

3.1 整体架构图(六边形架构 + DDD 分层)

graph TB
    subgraph "触发层 Trigger Layer"
        T1[HTTP Controller]
        T2[定时任务 Job]
        T3[消息监听 Listener]
    end
    
subgraph &quot;应用层 App Layer&quot;
    A1[Application.java&lt;br/&gt;启动入口]
    A2[Config 配置类]
end

subgraph &quot;领域层 Domain Layer - 核心业务&quot;
    D1[Model 模型层]
    D2[Service 服务层]
    D3[Adapter 适配器端口]
end

subgraph &quot;基础设施层 Infrastructure Layer&quot;
    I1[Repository 实现]
    I2[Port 外部接口实现]
    I3[DAO 数据访问]
    I4[Gateway 网关 RPC]
end

subgraph &quot;API 层 API Layer&quot;
    AP1[对外接口定义]
    AP2[DTO/Response]
end

subgraph &quot;类型层 Types Layer&quot;
    TP1[通用类型定义]
    TP2[枚举/常量]
    TP3[工具类]
end

T1 --&gt; D1
T2 --&gt; D1
T3 --&gt; D1
D1 --&gt; I1
D3 -.-&gt; I2
I1 --&gt; I3
I2 --&gt; I4

style D1 fill:#ff9999
style D2 fill:#ff9999
style D3 fill:#ff9999
style I1 fill:#99ccff
style I2 fill:#99ccff</code></pre>

4. 六大模块详解

4.1 my-mall-ddd-types(类型层)

目录结构:

my-mall-ddd-types/
└── src/main/java/cc/natapp1/sadsunset/types/
    ├── common/ # 通用类型
    │   └── Constants.java # 常量定义
    ├── enums/ # 枚举类型
    │   └── ResponseCode.java # 响应码枚举
    ├── exception/ # 异常定义
    │   └── AppException.java # 自定义异常
    └── weixin/ # 微信相关工具
        ├── MessageTextEntity.java
        ├── SignatureUtil.java
        └── XmlUtil.java

DDD 设计思想:

  • 共享内核(Shared Kernel):存放全项目通用的类型定义
  • 无状态工具类:提供跨领域的通用能力(如 XML 转换、签名验证)
  • 依赖方向:被所有其他模块依赖,但不依赖任何业务模块

4.2 my-mall-ddd-api(API 层)

目录结构:

my-mall-ddd-api/
└── src/main/java/cc/natapp1/sadsunset/api/
    ├── IAuthService.java # 认证服务接口
    ├── IPayService.java # 支付服务接口
    ├── dto/ # 数据传输对象
    │   └── package-info.java
    └── response/
        └── Response.java # 统一响应封装

DDD 设计思想:

  • 接口隔离:定义对外的服务契约(Contract)
  • 防腐层(ACL)雏形:为外部系统提供统一的访问入口
  • 依赖倒置:只定义接口,实现在其他层

4.3 my-mall-ddd-domain(领域层)⭐ 核心!

目录结构:

my-mall-ddd-domain/
└── src/main/java/cc/natapp1/sadsunset/domain/
    ├── auth/ # 认证领域
    │   ├── adapter/
    │   │   ├── port/ # 端口定义(对外部系统的抽象)
    │   │   │   └── ILoginPort.java
    │   │   └── repository/ # 仓储接口(未实现)
    │   └── service/ # 领域服务
    │       ├── ILoginService.java
    │       └── WeixinLoginService.java
    │
    └── order/ # 订单领域(核心业务)
        ├── adapter/
        │   ├── event/ # 领域事件
        │   │   └── PaySuccessMessageEvent.java
        │   ├── port/ # 端口定义
        │   │   └── IProductPort.java
        │   └── repository/ # 仓储接口
        │       └── IOrderRepository.java
        │
        ├── model/ # 领域模型
        │   ├── aggregate/ # 聚合根
        │   │   └── CreateOrderAggregate.java
        │   ├── entity/ # 实体
        │   │   ├── OrderEntity.java
        │   │   ├── ProductEntity.java
        │   │   ├── ShopCartEntity.java
        │   │   └── PayOrderEntity.java
        │   └── valobj/ # 值对象
        │       └── OrderStatusVO.java
        │
        └── service/ # 领域服务
            ├── AbstractOrderService.java # 抽象服务(模板方法)
            ├── IOrderService.java # 服务接口
            └── OrderService.java # 具体实现

DDD 设计思想:

1. 领域模型(Model)
// 聚合根 - CreateOrderAggregate
// 作用:封装"创建订单"这个完整业务流程
public class CreateOrderAggregate {
    private String userId; // 用户 ID
    private ProductEntity productEntity; // 商品实体
    private OrderEntity orderEntity; // 订单实体
    
    // 工厂方法 - 封装订单构建逻辑
    public static OrderEntity buildOrderEntity(String productId, String productName) {
        return OrderEntity.builder()
            .productId(productId)
            .productName(productName)
            .orderId(RandomStringUtils.randomNumeric(14))
            .orderTime(new Date())
            .orderStatusVO(OrderStatusVO.CREATE)
            .build();
    }
}

为什么需要聚合根? - ✅ 一致性边界:确保用户、商品、订单作为一个整体被处理 - ✅ 业务封装:外部不需要关心订单如何构建 - ✅ 事务边界:一个聚合根内的操作在一个事务中完成

2. 实体 vs 值对象
// 实体 - OrderEntity(有唯一标识 orderId)
public class OrderEntity {
    private String orderId; // 🔑 唯一标识
    private Date orderTime;
    private BigDecimal totalAmount;
    private OrderStatusVO orderStatusVO; // 引用值对象
}

// 值对象 - OrderStatusVO(无唯一标识,只有语义)
public enum OrderStatusVO {
    CREATE("CREATE", "创建完成"),
    PAY_WAIT("PAY_WAIT", "等待支付"),
    PAY_SUCCESS("PAY_SUCCESS", "支付成功");
    
    private final String code;
    private final String desc;
}
3. 领域服务(Service)
// 抽象服务 - 使用模板方法模式
public abstract class AbstractOrderService implements IOrderService {
    
    protected final IOrderRepository repository;
    protected final IProductPort port;
    
    // 构造函数注入依赖
    public AbstractOrderService(IOrderRepository repository, IProductPort port) {
        this.repository = repository;
        this.port = port;
    }
    
    // 通用流程(所有订单创建都一样)
    @Override
    public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception {
        // 1. 查询未支付订单
        OrderEntity unpaidOrderEntity = repository.queryUnPayOrder(shopCartEntity);
        
        // 2. 如果已存在未支付订单,直接返回
        if (null != unpaidOrderEntity && OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) {
            return PayOrderEntity.builder()
                .orderId(unpaidOrderEntity.getOrderId())
                .payUrl(unpaidOrderEntity.getPayUrl())
                .build();
        }
        
        // 3. 查询商品信息
        ProductEntity productEntity = port.queryProductByProductId(shopCartEntity.getProductId());
        
        // 4. 构建订单聚合根
        OrderEntity orderEntity = CreateOrderAggregate.buildOrderEntity(...);
        CreateOrderAggregate orderAggregate = CreateOrderAggregate.builder()
            .userId(shopCartEntity.getUserId())
            .productEntity(productEntity)
            .orderEntity(orderEntity)
            .build();
        
        // 5. 保存订单(❗调用抽象方法,子类实现)
        this.doSaveOrder(orderAggregate);
        
        return PayOrderEntity.builder()...build();
    }
    
    // ❗抽象方法 - 强制子类实现
    protected abstract void doSaveOrder(CreateOrderAggregate orderAggregate);
}

// 具体实现
@Service
public class OrderService extends AbstractOrderService {
    
    public OrderService(IOrderRepository repository, IProductPort port) {
        super(repository, port);
    }
    
    @Override
    protected void doSaveOrder(CreateOrderAggregate orderAggregate) {
        repository.doSaveOrder(orderAggregate);
    }
}
4. 端口(Port)- 六边形架构的核心
// 定义在 domain 层 - 对外部系统的抽象
public interface IProductPort {
    ProductEntity queryProductByProductId(String productId);
}

// 实现在 infrastructure 层
@Component
public class ProductPort implements IProductPort {
    
    private final ProductRPC productRPC; // 依赖外部 RPC 服务
    
    public ProductPort(ProductRPC productRPC) {
        this.productRPC = productRPC;
    }
    
    @Override
    public ProductEntity queryProductByProductId(String productId) {
        // 1. 调用外部 RPC
        ProductDTO productDTO = productRPC.queryProductByProductId(productId);
        
        // 2. DTO 转 Entity(防腐层)
        return ProductEntity.builder()
            .productId(productDTO.getProductId())
            .productName(productDTO.getProductName())
            .productDesc(productDTO.getProductDesc())
            .price(productDTO.getPrice())
            .build();
    }
}
5. 领域事件(Domain Event)
// PaySuccessMessageEvent.java
@Component
public class PaySuccessMessageEvent extends BaseEvent<PaySuccessMessageEvent.PaySuccessMessage> {
    
    @Override
    public EventMessage<PaySuccessMessage> buildEventMessage(PaySuccessMessage data) {
        return EventMessage.<PaySuccessMessage>builder()
            .id(RandomStringUtils.randomNumeric(11))
            .timestamp(new Date())
            .data(data)
            .build();
    }
    
    @Override
    public String topic() {
        return "pay_success";
    }
    
    // 事件消息体
    @Data
    @Builder
    public static class PaySuccessMessage {
        private String userId;
        private String tradeNo;
    }
}

作用: - 📨 解耦:支付成功后通知其他系统(如发送短信、更新积分) - 🔄 最终一致性:通过事件驱动实现分布式事务 - 📊 审计日志:记录重要的业务事件

4.4 my-mall-ddd-infrastructure(基础设施层)

目录结构:

my-mall-ddd-infrastructure/
└── src/main/java/cc/natapp1/sadsunset/infrastructure/
    ├── adapter/ # 适配器实现
    │   ├── port/ # Port 接口的实现
    │   │   ├── LoginPort.java
    │   │   └── ProductPort.java
    │   └── repository/ # Repository 接口的实现
    │       └── OrderRepository.java
    │
    ├── dao/ # 数据访问层(MyBatis Mapper)
    │   ├── IOrderDao.java # MyBatis 接口
    │   └── po/ # 持久化对象
    │       └── PayOrder.java # 对应数据库表
    │
    └── gateway/ # 外部网关(RPC/HTTP)
        ├── dto/ # 外部 DTO
        │   ├── ProductDTO.java
        │   ├── WeixinTokenResponseDTO.java
        │   └── ...
        ├── IWeixinApiService.java
        └── ProductRPC.java # 商品 RPC 服务

DDD 设计思想:

1. 仓储实现(Repository Implementation)

关键点: - 🔁 对象转换:PO ↔︎ Entity 的双向转换 - 🛡️ 防腐层:保护领域模型不受数据库模型污染 - 📦 依赖倒置:实现领域层定义的接口

4.5 my-mall-ddd-trigger(触发层)

目录结构:

my-mall-ddd-trigger/
└── src/main/java/cc/natapp1/sadsunset/trigger/
    ├── http/ # HTTP 控制器
    │   ├── AliPayController.java # 支付宝支付回调
    │   ├── LoginController.java # 登录接口
    │   └── WeixinPortalController.java # 微信公众号门户
    │
    ├── job/ # 定时任务
    │   └── package-info.java
    │
    └── listener/ # 消息监听器
        └── package-info.java

DDD 设计思想:

  • 用户接口(User Interface):接收外部请求,调用领域层
  • 薄 Controller:只做参数转换和结果返回,不包含业务逻辑
  • 依赖方向:只能依赖 Domain 层和 Types 层

4.6 my-mall-ddd-app(应用层)

目录结构:

my-mall-ddd-app/
└── src/main/java/cc/natapp1/sadsunset/
    ├── Application.java # Spring Boot 启动类
    ├── config/ # 配置类
    │   ├── GuavaConfig.java # Guava 事件总线配置
    │   ├── Retrofit2Config.java # Retrofit HTTP 客户端配置
    │   ├── ThreadPoolConfig.java # 线程池配置
    │   └── ThreadPoolConfigProperties.java
    │
    └── test/java/.../test/
        └── ApiTest.java # 测试类

DDD 设计思想:

  • 应用编排:理论上应该负责协调多个领域对象完成业务场景
  • 当前项目:主要是配置类和启动类,业务逻辑都在 Domain 层
  • 依赖关系:依赖所有其他模块

5. DDD 核心设计思想总结

5.1 战略设计(Strategic Design)

✅ 限界上下文(Bounded Context)

订单上下文(Order Context)
├── OrderEntity
├── OrderStatusVO
├── CreateOrderAggregate
└── IOrderService

认证上下文(Auth Context) ├── ILoginService └── WeixinLoginService

作用: 每个上下文有独立的模型定义,避免概念混淆

✅ 通用语言(Ubiquitous Language)

// 使用业务术语命名
OrderStatusVO.CREATE // 而不是 "0" 或 "NEW"
CreateOrderAggregate // 明确表达"创建订单"这个聚合
IOrderRepository // 仓储模式的标准命名

5.2 战术设计(Tactical Design)

✅ 实体(Entity)

  • 有唯一标识(如 orderId
  • 有生命周期(状态会变)
  • 示例:OrderEntityProductEntity

✅ 值对象(Value Object)

  • 无唯一标识
  • 不可变
  • 表达语义
  • 示例:OrderStatusVO

✅ 聚合根(Aggregate Root)

  • 一组相关对象的根
  • 外部只能通过聚合根访问
  • 保证一致性边界
  • 示例:CreateOrderAggregate

✅ 仓储(Repository)

// 领域层定义接口
public interface IOrderRepository {
    void doSaveOrder(CreateOrderAggregate orderAggregate);
    OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity);
}

// 基础设施层实现
@Repository
public class OrderRepository implements IOrderRepository {
    @Resource
    private IOrderDao orderDao;
    
    @Override
    public void doSaveOrder(CreateOrderAggregate orderAggregate) {
        // 实现细节...
    }
}

✅ 领域服务(Domain Service)

// 接口定义
public interface IOrderService {
    PayOrderEntity createOrder(ShopCartEntity shopCartEntity);
}

// 抽象实现(模板方法)
public abstract class AbstractOrderService implements IOrderService {
    // 通用流程
    public PayOrderEntity createOrder(...) { ... }
    
    // 抽象方法
    protected abstract void doSaveOrder(CreateOrderAggregate orderAggregate);
}

// 具体实现
@Service
public class OrderService extends AbstractOrderService {
    @Override
    protected void doSaveOrder(CreateOrderAggregate orderAggregate) {
        repository.doSaveOrder(orderAggregate);
    }
}

✅ 领域事件(Domain Event)

// 事件定义
public class PaySuccessMessageEvent extends BaseEvent<...> {
    public static class PaySuccessMessage {
        private String userId;
        private String tradeNo;
    }
}

// 事件发布
@Resource
private PaySuccessMessageEvent paySuccessMessageEvent;

@Resource
private EventBus eventBus;

public void changeOrderPaySuccess(String orderId) {
    // 更新订单状态...
    
    // 发布事件
    BaseEvent.EventMessage<PaySuccessMessage> eventMessage = 
        paySuccessMessageEvent.buildEventMessage(
            PaySuccessMessage.builder().tradeNo(orderId).build()
        );
    eventBus.post(eventMessage);
}

5.3 六边形架构(Hexagonal Architecture)

┌─────────────────┐
│ Trigger 层 │
│ (Controller) │
└────────┬────────┘
         │ 调用
┌────────▼────────┐
│ Domain 层 ⭐ │
│ (业务核心) │
│ ┌──────────┐ │
│ │ Port │◄──┼────── 依赖倒置
│ │ (接口) │ │
│ └──────────┘ │
└────────┬────────┘
         │ 实现
┌────────▼────────┐
│Infrastructure 层 │
│ (Repository 实现)│
└─────────────────┘

核心思想: - 🎯 领域中心:Domain 层是核心,不依赖外部 - 🔄 依赖倒置:外部依赖内部定义的接口(Port) - 🛡️ 防腐层:Infrastructure 层将外部 DTO 转为领域 Entity

5.4 依赖规则(Dependency Rule)

允许依赖方向:
Trigger → Domain → Infrastructure
 ↓ ↓ ↓
Types ← Types ← Types

禁止反向依赖: ❌ Infrastructure → Domain(只能实现接口,不能调用具体类) ❌ Domain → Trigger(领域层不知道有 Controller 存在)

6. 完整调用流程示例

用户下单流程:

1. 用户发起请求
 ↓
2. AliPayController.createPayOrder()
 ├─ 接收 DTO: CreatePayRequestDTO
 └─ 转换为 Entity: ShopCartEntity
 ↓
3. IOrderService.createOrder()
 ↓
4. AbstractOrderService.createOrder()
 ├─ 查询未支付订单:repository.queryUnPayOrder()
 ├─ 查询商品信息:port.queryProductByProductId()
 ├─ 构建聚合根:CreateOrderAggregate
 └─ 调用抽象方法:doSaveOrder()
 ↓
5. OrderService.doSaveOrder()
 ↓
6. OrderRepository.doSaveOrder()
 ├─ Entity → PO
 └─ orderDao.insert()
 ↓
7. 数据库保存订单
 ↓
8. 返回 PayOrderEntity 给 Controller
 ↓
9. 返回 Response<String> 给用户

7. 项目亮点总结

特性 体现 好处
领域驱动 Domain 层独立且为核心 业务逻辑清晰,易于维护
依赖倒置 Port 接口定义在 Domain,实现在 Infrastructure 解耦内外依赖
贫血→充血 AbstractOrderService 封装通用逻辑 代码复用,规范统一
聚合根设计 CreateOrderAggregate 组织多实体 保证数据一致性
值对象语义化 OrderStatusVO 替代 String 类型安全,语义明确
领域事件 PaySuccessMessageEvent 解耦下游业务
防腐层 DTO ↔︎ Entity 转换 保护领域模型纯净
评论交流

文章目录