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 用户下单支付流程
- 用户通过前端调用
/api/v1/alipay/create_pay_order创建支付订单 OrderService接收请求,调用支付宝 SDK 生成支付页面 URL- 订单信息保存到数据库(状态:待支付)
- 返回支付 URL 给用户,用户完成支付
- 支付宝回调
/api/v1/alipay/alipay_notify_url - 验证签名后更新订单状态为支付成功
- 通过 Guava EventBus 发布支付成功事件
2.2 微信扫码登录流程
- 用户扫描微信公众号二维码
- 微信推送扫码事件到
/api/v1/weixin/portal/receive WeixinLoginService保存登录状态到 Guava Cache- 发送微信模板消息通知用户登录成功
- 前端轮询检查登录状态
2.3 定时任务补偿流程
- 超时关单任务(每 10 分钟):关闭超过 30 分钟未支付的订单
- 支付补偿任务(每 3 秒):主动查询支付宝,处理未接收到的支付回调
3. DDD 分层架构
3.1 整体架构图(六边形架构 + DDD 分层)
graph TB
subgraph "触发层 Trigger Layer"
T1[HTTP Controller]
T2[定时任务 Job]
T3[消息监听 Listener]
end
subgraph "应用层 App Layer"
A1[Application.java<br/>启动入口]
A2[Config 配置类]
end
subgraph "领域层 Domain Layer - 核心业务"
D1[Model 模型层]
D2[Service 服务层]
D3[Adapter 适配器端口]
end
subgraph "基础设施层 Infrastructure Layer"
I1[Repository 实现]
I2[Port 外部接口实现]
I3[DAO 数据访问]
I4[Gateway 网关 RPC]
end
subgraph "API 层 API Layer"
AP1[对外接口定义]
AP2[DTO/Response]
end
subgraph "类型层 Types Layer"
TP1[通用类型定义]
TP2[枚举/常量]
TP3[工具类]
end
T1 --> D1
T2 --> D1
T3 --> D1
D1 --> I1
D3 -.-> I2
I1 --> I3
I2 --> 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)
- 有生命周期(状态会变)
- 示例:
OrderEntity、ProductEntity
✅ 值对象(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 转换
保护领域模型纯净