传统的的Spring MVC三层架构在应对复杂业务时显得捉襟见肘,DDD结构通过分层与对象职责分离,为系统架构注入新的活力。
引言:当MVC架构遇上复杂业务
在传统的Spring MVC或三层架构中,我们通常能看到这样的分层结构:
java
├── controller
│ └── OrderController.java
├── service
│ └── OrderService.java
├── dao
│ └── OrderDao.java
└── entity
└── Order.java
这里的Order实体类通常扮演多重角色:
-
数据库映射 - 通过JPA或MyBatis注解与表结构绑定
-
业务逻辑载体 - 包含部分业务方法
-
数据传输对象 - 在Controller、Service间传递
这种设计在项目初期看似简洁高效,但随着业务复杂度的增加,问题逐渐暴露:
-
业务与数据强耦合:数据库表结构调整会直接冲击业务逻辑
-
职责混乱:一个实体类承载过多职责,违反单一职责原则
-
模型表达力不足:简单的属性字段难以表达复杂的业务概念
-
难以测试:业务逻辑与持久化框架深度绑定
一、DDD的分层架构革命
DDD(领域驱动设计)通过清晰的分层架构,将业务复杂性内聚到领域模型中,同时将技术复杂性隔离到基础设施中。
1.1 传统DDD四层架构
java
├── interfaces(用户界面层)
│ └── OrderController.java
├── application(应用层)
│ └── OrderAppService.java
├── domain(领域层)
│ ├── model
│ │ ├── aggregate
│ │ │ └── Order.java(聚合根)
│ │ └── valueobject
│ │ └── Money.java
│ └── service
│ └── OrderDomainService.java
└── infrastructure(基础设施层)
└── persistence
├── OrderEntity.java(持久化实体)
└── OrderRepositoryImpl.java
1.2 核心拆分:领域对象与数据对象分离
DDD最核心的变革之一,就是将原来"万能"的Entity拆分:
|------------------|------------------|------------------------|----------------|
| 传统MVC Entity | DDD拆解结果 | 所在分层 | 核心职责 |
| 数据库映射 + 业务方法 | 聚合根 | domain | 封装业务逻辑,维护边界一致性 |
| 数据库映射 + 业务方法 | 基础设施层Entity | infrastructure | 纯粹的数据持久化 |
| 数据传输 | DTO | application/interfaces | 跨层数据传输 |
| 数据传输 | VO | interfaces | 前端展示专用 |
| 业务概念表达 | Value Object | domain | 描述不可变业务概念 |
二、深入理解DDD中的各类对象
2.1 聚合根(Aggregate Root) - 业务逻辑的守护者
聚合根是DDD的核心概念,它不仅是业务对象的容器,更是业务规则的执行者。
java
/**
* @Author: 洛洛起不来
* 订单聚合根示例
* 位置:domain/model/aggregate/
*/
public class Order implements AggregateRoot {
private OrderId id;
private CustomerId customerId;
private OrderStatus status;
private Money totalAmount;
private List<OrderItem> items; // 聚合内实体
// 核心业务方法
public void placeOrder() {
validateOrder();
this.status = OrderStatus.PLACED;
this.addDomainEvent(new OrderPlacedEvent(this.id));
}
public void cancelOrder(String reason) {
if (!this.canBeCanceled()) {
throw new OrderCannotCancelException("订单已发货,无法取消");
}
this.status = OrderStatus.CANCELLED;
this.addDomainEvent(new OrderCancelledEvent(this.id, reason));
}
// 业务规则校验
private void validateOrder() {
if (items.isEmpty()) {
throw new EmptyOrderException("订单不能为空");
}
if (totalAmount.isNegative()) {
throw new InvalidAmountException("订单金额不能为负");
}
}
}
聚合根的特点:
-
是整个聚合的唯一入口,外部只能通过聚合根操作聚合内对象
-
封装业务逻辑,维护聚合内数据的一致性
-
是领域模型的核心,与技术实现无关
-
可以发布领域事件,实现领域间的解耦通信
2.2 基础设施层Entity - 纯粹的数据容器
java
/**
* @Author: 洛洛起不来
* 订单持久化实体
* 位置:infrastructure/persistence/
*/
@Entity
@Table(name = "t_order")
public class OrderEntity {
@Id
private Long id;
@Column(name = "customer_id")
private Long customerId;
@Column(name = "total_amount")
private BigDecimal totalAmount;
@Column(name = "status")
private Integer status;
@Column(name = "created_at")
private LocalDateTime createdAt;
// 只有getter/setter,没有业务逻辑
// 纯粹的贫血模型
}
基础设施层Entity的特点:
-
只包含数据和映射关系,没有任何业务逻辑
-
与具体的ORM框架(JPA、MyBatis)强绑定
-
负责在数据库和领域模型之间进行数据转换
-
一个聚合根可能对应多个基础设施Entity
2.3 值对象(Value Object) - 业务概念的精准表达
java
/**
* 货币值对象
* 通过属性值定义,而非标识
*/
@Value
public class Money implements ValueObject {
private final BigDecimal amount;
private final String currency;
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException("货币类型不匹配");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money multiply(BigDecimal multiplier) {
return new Money(this.amount.multiply(multiplier), this.currency);
}
public boolean isGreaterThan(Money other) {
return this.amount.compareTo(other.amount) > 0;
}
// 值对象的相等性比较基于属性值
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 &&
currency.equals(money.currency);
}
}
值对象的特点:
-
通过属性值定义,而不是标识(ID)
-
不可变(immutable),创建后状态不可修改
-
描述无状态的业务概念
-
可包含领域行为(如Money的加减运算)
2.4 DTO与VO - 数据传输与展示的桥梁
java
/**
* DTO:应用层数据传输
* 位置:application/dto/
*/
@Data
public class CreateOrderDTO {
private Long customerId;
private List<OrderItemDTO> items;
private String deliveryAddress;
private String paymentMethod;
}
/**
* VO:前端展示专用
* 位置:interfaces/vo/
*/
@Data
public class OrderDetailVO {
private String orderId;
private String orderNo;
private String customerName;
private String statusText;
private String totalAmount;
private String createTime;
private List<ProductInfoVO> products; // 聚合了其他领域的数据
private String deliveryAddress;
private String paymentInfo;
}
DTO与VO的区别:
|----------|----------------------------|-------------|
| 特性 | DTO | VO |
| 使用场景 | 跨层数据传输,如Service到Controller | 前端展示专用 |
| 数据来源 | 通常对应单个领域模型 | 可能聚合多个领域的数据 |
| 格式处理 | 保持原数据格式 | 可能进行格式化、裁剪 |
| 职责 | 解耦领域模型和接口 | 适配前端展示需求 |
三、对象间的协作流程
以一个电商"下单"业务为例,展示各对象如何协同工作:
java
/**
* 完整的下单流程示例
*/
@Service
public class OrderAppService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductService productService;
@Transactional
public OrderResultVO placeOrder(CreateOrderDTO dto) {
// 参数校验
validateCreateOrderDTO(dto);
// 创建聚合根
Order order = OrderFactory.createOrder(dto);
// 调用领域服务检查库存
productService.checkInventory(order.getProductItems());
// 执行业务逻辑
order.placeOrder();
// 保存聚合根
orderRepository.save(order);
// 发布应用事件(可选)
applicationEventPublisher.publishEvent(
new OrderCreatedEvent(order.getId())
);
// 返回前端VO
return OrderAssembler.toVO(order);
}
}
具体的数据流转:

四、为什么需要这么多对象类型?
4.1 单一职责原则的极致体现
DDD将原来"一肩挑"的Entity拆分为多个对象,每个对象都有明确的职责:
-
聚合根:负责业务规则
-
值对象:负责业务概念表达
-
基础设施Entity:负责数据持久化
-
DTO:负责跨层数据传输
-
VO:负责前端展示适配
4.2 解耦带来的架构优势
业务与数据持久化解耦
java
// 传统方式:业务逻辑与JPA注解混杂
@Entity
public class Order {
@Id
private Long id;
@Column(name = "order_no")
private String orderNo;
// 业务逻辑中掺杂了数据库字段映射
public boolean isOverdue() {
return status == 1 && createdTime.plusDays(7).isBefore(LocalDateTime.now());
}
}
// DDD方式:业务逻辑独立
public class Order implements AggregateRoot {
private OrderStatus status;
private LocalDateTime createdTime;
// 纯粹的领域逻辑
public boolean isOverdue() {
return status == OrderStatus.UNPAID &&
createdTime.plusDays(7).isBefore(LocalDateTime.now());
}
}
领域模型和外部接口解耦
java
// 领域模型稳定不变
public class User {
private UserId id;
private String username;
private Email email;
private PhoneNumber phone;
}
// 接口模型可独立演化
public class UserDTO {
private String id;
private String name;
private String email;
private String phone;
// 适配不同接口需求
public static UserDTO from(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId().getValue());
dto.setName(user.getUsername());
dto.setEmail(user.getEmail().getValue());
dto.setPhone(user.getPhone().getFormattedNumber());
return dto;
}
}
4.3 应对变化的架构弹性
场景1:数据库从MySQL迁移到MongoDB
java
// 传统架构:需要修改所有Entity类
@Entity // 需要删除JPA注解
@Document(collection = "orders") // 添加MongoDB注解
public class Order {
// 需要修改所有字段映射
}
// DDD架构:只需修改基础设施层
// 领域层不变
public class Order implements AggregateRoot {
// 业务逻辑保持不变
}
// 基础设施层适配
@Document(collection = "orders")
public class OrderDocument {
// MongoDB特定的映射
@Id
private String mongoId;
private String orderId;
// 其他字段...
}
场景2:前端需求变化,需要新字段
java
// 传统架构:可能需要修改数据库和Entity
@Entity
public class Order {
// 需要添加新字段
private String newFieldForFrontend;
}
// DDD架构:只需修改VO
public class OrderDetailVO {
// 添加前端需要的新字段
private String newDisplayField;
public static OrderDetailVO from(Order order) {
OrderDetailVO vo = new OrderDetailVO();
vo.setNewDisplayField(order.calculateNewField());
return vo;
}
}
五、总结
架构的本质是在各种约束下做出权衡。DDD通过增加短期复杂度,换取长期的可维护性和业务适应能力。当你的系统需要应对频繁变化的复杂业务时,这种"复杂"的拆分反而成为了对抗真正混乱的最佳武器。
进一步学习资源:
-
《领域驱动设计:软件核心复杂性应对之道》- Eric Evans
-
《实现领域驱动设计》- Vaughn Vernon
作者观点:DDD不是最完美的开发方式,但它提供了一套系统的思考框架,帮助我们在业务复杂性与技术实现之间找到平衡点。记住,好的架构不是一开始就设计出来的,而是在不断演进中生长出来的。