传统的的Spring MVC三层架构在应对复杂业务时显得捉襟见肘,DDD结构通过分层与对象职责分离,为系统架构注入新的活力。

引言:当MVC架构遇上复杂业务

在传统的Spring MVC或三层架构中,我们通常能看到这样的分层结构:

├── controller
│   └── OrderController.java
├── service
│   └── OrderService.java
├── dao
│   └── OrderDao.java
└── entity
    └── Order.java

这里的Order实体类通常扮演多重角色:

  1. 数据库映射​ - 通过JPA或MyBatis注解与表结构绑定

  2. 业务逻辑载体​ - 包含部分业务方法

  3. 数据传输对象​ - 在Controller、Service间传递

这种设计在项目初期看似简洁高效,但随着业务复杂度的增加,问题逐渐暴露:

  • 业务与数据强耦合:数据库表结构调整会直接冲击业务逻辑

  • 职责混乱:一个实体类承载过多职责,违反单一职责原则

  • 模型表达力不足:简单的属性字段难以表达复杂的业务概念

  • 难以测试:业务逻辑与持久化框架深度绑定

一、DDD的分层架构革命

DDD(领域驱动设计)通过清晰的分层架构,将业务复杂性内聚到领域模型中,同时将技术复杂性隔离到基础设施中。

1.1 传统DDD四层架构

├── 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的核心概念,它不仅是业务对象的容器,更是业务规则的执行者。

/**
 * @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 - 纯粹的数据容器

/**
 * @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) - 业务概念的精准表达

/**
 * 货币值对象
 * 通过属性值定义,而非标识
 */
@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 - 数据传输与展示的桥梁

/**
 * 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 前端展示专用
数据来源 通常对应单个领域模型 可能聚合多个领域的数据
格式处理 保持原数据格式 可能进行格式化、裁剪
职责 解耦领域模型和接口 适配前端展示需求

三、对象间的协作流程

以一个电商"下单"业务为例,展示各对象如何协同工作:

/**
 * 完整的下单流程示例
 */
@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 解耦带来的架构优势

业务与数据持久化解耦

// 传统方式:业务逻辑与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());
    }
}

领域模型和外部接口解耦

// 领域模型稳定不变
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

// 传统架构:需要修改所有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:前端需求变化,需要新字段

// 传统架构:可能需要修改数据库和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

  • 示例项目:https://github.com/ddd-by-examples

作者观点:DDD不是最完美的开发方式,但它提供了一套系统的思考框架,帮助我们在业务复杂性与技术实现之间找到平衡点。记住,好的架构不是一开始就设计出来的,而是在不断演进中生长出来的。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐