DDD-021:Spring Boot + DDD 项目结构
21.1 项目结构概述
21.1.1 为什么项目结构如此重要?
【原理】
项目结构是软件架构的外在表现,它直接影响:
DDD 强调项目结构应该反映领域模型,而不是技术实现。一个好的 DDD 项目结构应该让新人一眼就能看出系统的业务领域。
【历史架构问题】
传统 Spring Boot 项目的结构问题:
❌ 传统技术分层结构
src/main/java/com/example/
├── controller/ # 所有 Controller 混在一起
│ ├── OrderController.java
│ ├── UserController.java
│ ├── ProductController.java
│ └── PaymentController.java
├── service/ # 所有 Service 混在一起
│ ├── OrderService.java
│ ├── UserService.java
│ └── ProductService.java
├── dao/ # 所有 DAO 混在一起
│ ├── OrderDao.java
│ ├── UserDao.java
│ └── ProductDao.java
├── entity/ # 贫血实体
│ ├── Order.java
│ ├── User.java
│ └── Product.java
├── dto/ # 所有 DTO 混在一起
│ ├── OrderDTO.java
│ └── UserDTO.java
└── util/ # 工具类
└── DateUtil.java
存在问题:
| 业务边界模糊 | 订单、用户、商品代码混杂 | 难以理解业务 |
| 修改范围大 | 改一个功能涉及多个目录 | 重构成本高 |
| 团队冲突 | 多人修改同一目录 | 合并频繁冲突 |
| 领域模型弱化 | entity 包只有贫血对象 | 业务逻辑散落 |
真实案例:某电商平台
初期团队5人,按技术分层开发
├── 代码量:10万行
├── 问题:业务逻辑分散在 Service 层
├── 6个月后:新功能开发变慢,Bug 增加
└── 1年后:不敢改动代码,怕引入 Bug
根本原因:项目结构没有体现业务边界
【DDD 如何解决】
DDD 推荐的项目结构按业务领域组织,而非技术分层:
✅ DDD 领域驱动结构
src/main/java/com/example/
├── order/ # 订单上下文(一个完整的业务边界)
│ ├── api/ # 接口层
│ │ ├── controller/
│ │ ├── dto/
│ │ └── assembler/
│ ├── application/ # 应用层
│ │ ├── service/
│ │ └── command/
│ ├── domain/ # 领域层(核心)
│ │ ├── model/
│ │ ├── repository/
│ │ ├── service/
│ │ └── event/
│ └── infrastructure/ # 基础设施层
│ ├── repository/
│ ├── gateway/
│ └── config/
├── product/ # 商品上下文
│ ├── api/
│ ├── application/
│ ├── domain/
│ └── infrastructure/
├── user/ # 用户上下文
└── payment/ # 支付上下文
【设计优势】
| 业务边界 | 模糊 | 清晰 |
| 代码定位 | 需要记忆目录 | 按业务直觉定位 |
| 团队协作 | 冲突频繁 | 按上下文分配 |
| 重构范围 | 牵一发动全身 | 影响单个上下文 |
| 新人理解 | 需要阅读代码 | 看目录结构即懂 |
21.2 项目模块划分策略
21.2.1 单模块 vs 多模块
【原理】
Spring Boot 项目有两种模块划分策略:
选择依据:
| 5人以下 | 初期 | 单模块 |
| 5-10人 | 发展中 | 单模块 + 包隔离 |
| 10人以上 | 成熟 | 多模块 |
| 微服务 | 任意 | 每个服务独立模块 |
【历史架构问题】
两种极端:
❌ 极端1:过早多模块(团队3人)
ecommerce/
├── order-module/
├── product-module/
├── user-module/
├── payment-module/
└── common-module/
问题:
1. 构建时间长(每个模块独立编译)
2. 模块间依赖复杂
3. 调试困难(跨模块断点)
4. 团队规模小,维护成本高
❌ 极端2:始终单模块(团队30人)
ecommerce/
└── src/main/java/com/example/
└── … 100万行代码
问题:
1. 编译时间超长(10分钟+)
2. 部署风险大(改一行重新部署)
3. 团队协作冲突频繁
4. 无法独立扩展
【DDD 如何解决】
渐进式模块化策略:
阶段1:单模块 + 包隔离(0-6个月,团队5-10人)
ecommerce-app/
├── src/main/java/com/example/
│ ├── order/ # 订单上下文(包)
│ ├── product/ # 商品上下文(包)
│ └── user/ # 用户上下文(包)
└── pom.xml
特点:
– 快速开发
– 清晰的业务边界
– 低维护成本
阶段2:模块化单体(6-12个月,团队10-20人)
ecommerce-app/
├── order-module/ # 订单模块
│ ├── src/main/java/
│ └── pom.xml
├── product-module/ # 商品模块
├── user-module/ # 用户模块
├── common-module/ # 公共模块
└── pom.xml (父POM)
特点:
– 模块间依赖明确
– 可以独立测试
– 为微服务做准备
阶段3:微服务(12个月+,团队20+人)
ecommerce-system/
├── order-service/ # 订单微服务
│ ├── src/
│ └── pom.xml
├── product-service/ # 商品微服务
├── user-service/ # 用户微服务
└── pom.xml (父POM)
特点:
– 独立部署
– 独立扩展
– 团队独立自治
【设计优势】
| 初期开发效率 | 低 | 高 |
| 模块边界清晰度 | 低(需要提前设计) | 高(实践中验证) |
| 团队适应度 | 需要学习 | 渐进学习 |
| 技术债务 | 高(设计过度) | 低(按需演进) |
21.2.2 包结构设计原则
【原理】
DDD 项目包结构的核心原则:
两种常见的包结构:
方式1:按技术分层分包(在领域包内部)
com.example.order/ # 订单上下文
├── api/ # 接口层
│ ├── controller/
│ │ └── OrderController.java
│ ├── dto/
│ │ ├── CreateOrderRequest.java
│ │ └── OrderResponse.java
│ └── assembler/
│ └── OrderAssembler.java
├── application/ # 应用层
│ ├── service/
│ │ └── OrderApplicationService.java
│ ├── command/
│ │ ├── CreateOrderCommand.java
│ │ └── PayOrderCommand.java
│ └── query/
│ └── OrderQueryService.java
├── domain/ # 领域层(核心)
│ ├── model/
│ │ ├── Order.java # 聚合根
│ │ ├── OrderItem.java # 实体
│ │ ├── OrderId.java # 值对象
│ │ └── OrderStatus.java # 枚举
│ ├── repository/
│ │ └── OrderRepository.java
│ ├── service/
│ │ └── PricingService.java
│ └── event/
│ ├── OrderCreatedEvent.java
│ └── OrderPaidEvent.java
└── infrastructure/ # 基础设施层
├── repository/
│ ├── OrderJpaRepository.java
│ ├── JpaOrderRepository.java
│ └── OrderPO.java
├── gateway/
│ └── ProductGatewayImpl.java
└── config/
└── OrderConfig.java
方式2:按领域概念分包(更推荐)
com.example.order/ # 订单上下文
├── application/ # 应用层
│ ├── OrderApplicationService.java
│ ├── CreateOrderCommand.java
│ └── PayOrderCommand.java
├── domain/ # 领域层(核心)
│ ├── Order.java # 聚合根
│ ├── OrderItem.java # 实体
│ ├── OrderId.java # 值对象
│ ├── OrderRepository.java # 仓储接口
│ └── OrderStatus.java # 枚举
├── infrastructure/ # 基础设施层
│ ├── persistence/
│ │ ├── OrderJpaRepository.java
│ │ ├── JpaOrderRepository.java
│ │ └── OrderPO.java
│ └── gateway/
│ └── ProductGatewayImpl.java
└── interfaces/ # 接口层
├── controller/
│ └── OrderController.java
├── dto/
│ ├── CreateOrderRequest.java
│ └── OrderResponse.java
└── assembler/
└── OrderAssembler.java
【历史架构问题】
❌ 传统技术分包(跨领域)
com.example/
├── controller/
│ ├── OrderController.java # 订单
│ ├── UserController.java # 用户(跨领域混在一起)
│ └── ProductController.java # 商品
├── service/
│ ├── OrderService.java
│ ├── UserService.java
│ └── ProductService.java
└── dao/
├── OrderDao.java
└── UserDao.java
问题:
1. 改一个订单功能,需要跳转多个目录
2. 删除一个功能,需要搜索整个项目
3. 新人理解业务困难,代码分散
【DDD 如何解决】
✅ DDD 领域分包(内聚)
com.example/
├── order/ # 订单上下文(内聚)
│ ├── application/
│ │ └── OrderApplicationService.java
│ ├── domain/
│ │ ├── Order.java
│ │ └── OrderRepository.java
│ ├── infrastructure/
│ │ └── JpaOrderRepository.java
│ └── interfaces/
│ ├── OrderController.java
│ └── CreateOrderRequest.java
├── user/ # 用户上下文
│ ├── application/
│ ├── domain/
│ ├── infrastructure/
│ └── interfaces/
└── product/ # 商品上下文
优势:
1. 改订单功能,只需要在 order 包下修改
2. 删除订单功能,删除 order 包即可
3. 新人看包名就知道业务
【代码示例】
// ========== 包结构示例:订单上下文 ==========
// domain/model/Order.java – 聚合根
package com.example.order.domain;
public class Order extends AggregateRoot<OrderId> {
private OrderId id;
private OrderStatus status;
private List<OrderItem> items;
public static Order create(CustomerId customerId, List<OrderItem> items) {
Order order = new Order();
order.id = OrderId.generate();
order.status = OrderStatus.CREATED;
order.items = items;
return order;
}
public void pay(PaymentId paymentId) {
if (this.status != OrderStatus.CREATED) {
throw new InvalidOrderStateException(\”只能支付待支付的订单\”);
}
this.status = OrderStatus.PAID;
this.registerEvent(new OrderPaidEvent(this.id, paymentId));
}
}
// domain/repository/OrderRepository.java – 仓储接口
package com.example.order.domain;
public interface OrderRepository {
Order findById(OrderId id);
void save(Order order);
List<Order> findByCustomerId(CustomerId customerId);
}
// application/OrderApplicationService.java – 应用服务
package com.example.order.application;
@Service
@Transactional
public class OrderApplicationService {
private final OrderRepository orderRepository;
public OrderId createOrder(CreateOrderCommand command) {
Order order = Order.create(
command.getCustomerId(),
command.getItems()
);
orderRepository.save(order);
return order.getId();
}
}
// infrastructure/persistence/JpaOrderRepository.java – 仓储实现
package com.example.order.infrastructure.persistence;
@Repository
public class JpaOrderRepository implements OrderRepository {
@Autowired
private OrderJpaRepository jpaRepository;
@Override
public Order findById(OrderId id) {
return jpaRepository.findById(id.getValue())
.map(OrderMapper::toDomain)
.orElse(null);
}
}
// interfaces/controller/OrderController.java – 控制器
package com.example.order.interfaces.controller;
@RestController
@RequestMapping(\”/api/orders\”)
public class OrderController {
private final OrderApplicationService orderService;
@PostMapping
public OrderId createOrder(@RequestBody CreateOrderRequest request) {
CreateOrderCommand command = OrderAssembler.toCommand(request);
return orderService.createOrder(command);
}
}
21.3 完整项目结构示例
21.3.1 单模块项目结构
【代码示例】
ecommerce-app/ # 项目根目录
├── pom.xml # Maven 配置
├── src/
│ ├── main/
│ │ ├── java/com/example/ecommerce/
│ │ │ ├── EcommerceApplication.java # 启动类
│ │ │ │
│ │ │ ├── order/ # ========== 订单上下文 ==========
│ │ │ │ ├── application/ # 应用层
│ │ │ │ │ ├── OrderApplicationService.java
│ │ │ │ │ ├── command/
│ │ │ │ │ │ ├── CreateOrderCommand.java
│ │ │ │ │ │ ├── PayOrderCommand.java
│ │ │ │ │ │ └── CancelOrderCommand.java
│ │ │ │ │ └── query/
│ │ │ │ │ └── OrderQueryService.java
│ │ │ │ │
│ │ │ │ ├── domain/ # 领域层(核心)
│ │ │ │ │ ├── model/
│ │ │ │ │ │ ├── Order.java # 聚合根
│ │ │ │ │ │ ├── OrderItem.java # 实体
│ │ │ │ │ │ ├── OrderId.java # 值对象
│ │ │ │ │ │ └── OrderStatus.java# 枚举
│ │ │ │ │ ├── repository/
│ │ │ │ │ │ └── OrderRepository.java
│ │ │ │ │ ├── service/
│ │ │ │ │ │ └── PricingService.java
│ │ │ │ │ └── event/
│ │ │ │ │ ├── OrderCreatedEvent.java
│ │ │ │ │ └── OrderPaidEvent.java
│ │ │ │ │
│ │ │ │ ├── infrastructure/ # 基础设施层
│ │ │ │ │ ├── persistence/
│ │ │ │ │ │ ├── OrderJpaRepository.java
│ │ │ │ │ │ ├── JpaOrderRepository.java
│ │ │ │ │ │ ├── OrderPO.java
│ │ │ │ │ │ └── OrderMapper.java
│ │ │ │ │ ├── gateway/
│ │ │ │ │ │ ├── ProductGatewayImpl.java
│ │ │ │ │ │ └── PaymentGatewayImpl.java
│ │ │ │ │ └── config/
│ │ │ │ │ └── OrderConfig.java
│ │ │ │ │
│ │ │ │ └── interfaces/ # 接口层
│ │ │ │ ├── controller/
│ │ │ │ │ └── OrderController.java
│ │ │ │ ├── dto/
│ │ │ │ │ ├── request/
│ │ │ │ │ │ ├── CreateOrderRequest.java
│ │ │ │ │ │ └── PayOrderRequest.java
│ │ │ │ │ └── response/
│ │ │ │ │ ├── OrderResponse.java
│ │ │ │ │ └── OrderListResponse.java
│ │ │ │ └── assembler/
│ │ │ │ └── OrderAssembler.java
│ │ │ │
│ │ │ ├── product/ # ========== 商品上下文 ==========
│ │ │ │ ├── application/
│ │ │ │ ├── domain/
│ │ │ │ ├── infrastructure/
│ │ │ │ └── interfaces/
│ │ │ │
│ │ │ ├── inventory/ # ========== 库存上下文 ==========
│ │ │ │ ├── application/
│ │ │ │ ├── domain/
│ │ │ │ ├── infrastructure/
│ │ │ │ └── interfaces/
│ │ │ │
│ │ │ ├── payment/ # ========== 支付上下文 ==========
│ │ │ │ ├── application/
│ │ │ │ ├── domain/
│ │ │ │ ├── infrastructure/
│ │ │ │ └── interfaces/
│ │ │ │
│ │ │ ├── user/ # ========== 用户上下文 ==========
│ │ │ │ ├── application/
│ │ │ │ ├── domain/
│ │ │ │ ├── infrastructure/
│ │ │ │ └── interfaces/
│ │ │ │
│ │ │ └── common/ # ========== 公共模块 ==========
│ │ │ ├── domain/
│ │ │ │ ├── AggregateRoot.java
│ │ │ │ ├── Entity.java
│ │ │ │ ├── ValueObject.java
│ │ │ │ └── DomainEvent.java
│ │ │ ├── exception/
│ │ │ │ ├── BusinessException.java
│ │ │ │ └── EntityNotFoundException.java
│ │ │ └── util/
│ │ │ └── Money.java
│ │ │
│ │ └── resources/
│ │ ├── application.yml # 主配置文件
│ │ ├── application-dev.yml # 开发环境配置
│ │ ├── application-test.yml # 测试环境配置
│ │ ├── application-prod.yml # 生产环境配置
│ │ └── db/
│ │ └── migration/ # 数据库迁移脚本
│ │
│ └── test/java/com/example/ecommerce/
│ ├── order/
│ │ ├── domain/
│ │ │ └── OrderTest.java # 领域层单元测试
│ │ └── application/
│ │ └── OrderApplicationServiceTest.java
│ └── product/
│
└── docs/ # 项目文档
├── architecture.md # 架构说明
├── domain-model.md # 领域模型说明
└── api/ # API 文档
└── order-api.md
21.3.2 多模块项目结构
【代码示例】
ecommerce-platform/ # 项目根目录
├── pom.xml # 父 POM
│
├── ecommerce-common/ # 公共模块
│ ├── pom.xml
│ └── src/main/java/com/example/common/
│ ├── domain/
│ │ ├── AggregateRoot.java
│ │ ├── Entity.java
│ │ ├── ValueObject.java
│ │ └── DomainEvent.java
│ ├── exception/
│ │ ├── BusinessException.java
│ │ └── EntityNotFoundException.java
│ └── util/
│ ├── Money.java
│ └── AuditInfo.java
│
├── ecommerce-order/ # 订单模块
│ ├── pom.xml
│ ├── src/main/java/com/example/order/
│ │ ├── application/
│ │ ├── domain/
│ │ ├── infrastructure/
│ │ └── interfaces/
│ └── src/test/java/com/example/order/
│
├── ecommerce-product/ # 商品模块
│ ├── pom.xml
│ └── src/main/java/com/example/product/
│
├── ecommerce-inventory/ # 库存模块
│ ├── pom.xml
│ └── src/main/java/com/example/inventory/
│
├── ecommerce-payment/ # 支付模块
│ ├── pom.xml
│ └── src/main/java/com/example/payment/
│
├── ecommerce-user/ # 用户模块
│ ├── pom.xml
│ └── src/main/java/com/example/user/
│
└── ecommerce-web/ # Web 应用(聚合所有模块)
├── pom.xml
└── src/main/java/com/example/
├── EcommerceApplication.java # 启动类
└── config/
├── WebConfig.java
└── SwaggerConfig.java
父 POM 示例:
<?xml version=\”1.0\” encoding=\”UTF-8\”?>
<project xmlns=\”http://maven.apache.org/POM/4.0.0\”
xmlns:xsi=\”http://www.w3.org/2001/XMLSchema-instance\”
xsi:schemaLocation=\”http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd\”>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ecommerce-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>ecommerce-common</module>
<module>ecommerce-order</module>
<module>ecommerce-product</module>
<module>ecommerce-inventory</module>
<module>ecommerce-payment</module>
<module>ecommerce-user</module>
<module>ecommerce-web</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<java.version>17</java.version>
<ddd.version>1.0.0</ddd.version>
</properties>
<dependencyManagement>
<dependencies>
<!– 公共模块 –>
<dependency>
<groupId>com.example</groupId>
<artifactId>ecommerce-common</artifactId>
<version>${project.version}</version>
</dependency>
<!– 订单模块 –>
<dependency>
<groupId>com.example</groupId>
<artifactId>ecommerce-order</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
模块 POM 示例(订单模块):
<?xml version=\”1.0\” encoding=\”UTF-8\”?>
<project xmlns=\”http://maven.apache.org/POM/4.0.0\”
xmlns:xsi=\”http://www.w3.org/2001/XMLSchema-instance\”
xsi:schemaLocation=\”http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd\”>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>ecommerce-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>ecommerce-order</artifactId>
<packaging>jar</packaging>
<dependencies>
<!– 公共模块 –>
<dependency>
<groupId>com.example</groupId>
<artifactId>ecommerce-common</artifactId>
</dependency>
<!– Spring Boot –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!– 测试 –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
21.4 各层详细设计
21.4.1 接口层(Interfaces Layer)
【原理】
接口层是系统与外部世界的交互边界,职责包括:
- 接收 HTTP 请求
- 参数验证
- 调用应用服务
- 响应封装
【代码示例】
// ========== 接口层目录结构 ==========
interfaces/
├── controller/
│ ├── OrderController.java # 订单控制器
│ └── OrderQueryController.java # 订单查询控制器
├── dto/
│ ├── request/
│ │ ├── CreateOrderRequest.java
│ │ ├── PayOrderRequest.java
│ │ └── CancelOrderRequest.java
│ └── response/
│ ├── OrderResponse.java
│ └── OrderListResponse.java
├── assembler/
│ └── OrderAssembler.java # DTO 转换器
└── facade/
└── OrderServiceFacade.java # 外观模式(可选)
// ========== OrderController.java ==========
package com.example.order.interfaces.controller;
@RestController
@RequestMapping(\”/api/v1/orders\”)
@Validated
@Tag(name = \”订单管理\”, description = \”订单相关接口\”)
public class OrderController {
private final OrderApplicationService orderService;
@PostMapping
@Operation(summary = \”创建订单\”)
public ApiResponse<CreateOrderResponse> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
CreateOrderCommand command = OrderAssembler.toCommand(request);
OrderId orderId = orderService.createOrder(command);
return ApiResponse.success(new CreateOrderResponse(orderId));
}
@PostMapping(\”/{orderId}/payment\”)
@Operation(summary = \”支付订单\”)
public ApiResponse<Void> payOrder(
@PathVariable String orderId,
@Valid @RequestBody PayOrderRequest request) {
PayOrderCommand command = new PayOrderCommand(
OrderId.of(orderId),
request.getPaymentMethod(),
request.getPassword()
);
orderService.payOrder(command);
return ApiResponse.success();
}
@PostMapping(\”/{orderId}/cancellation\”)
@Operation(summary = \”取消订单\”)
public ApiResponse




