🧠知识图谱

MVC 架构回顾
传统 MVC(Model-View-Controller)架构是大多数 Java 开发者的起点,典型的三层架构如下:

典型的 MVC 项目结构
com.example
├── controller/
│ ├── OrderController.java
│ └── UserController.java
├── service/
│ ├── OrderService.java ← 所有订单业务逻辑堆在这里
│ └── UserService.java
├── mapper/
│ ├── OrderMapper.java
│ └── UserMapper.java
└── entity/
├── Order.java ← 只有 getter/setter,没有行为
└── User.java
MVC 的痛点
痛点一:贫血模型导致业务逻辑分散
java
// ❌ 贫血模型:Order 只是数据容器
@Data
public class Order {
private Long id;
private String status;
private BigDecimal totalAmount;
private List<OrderItem> items;
// 只有 getter/setter,无业务方法
}
// ❌ 业务逻辑全部堆在 Service 层
@Service
public class OrderService {
public void cancelOrder(Long orderId) {
Order order = orderMapper.findById(orderId);
// 状态校验逻辑散落在 Service
if (!"PENDING".equals(order.getStatus())) {
throw new BusinessException("只有待支付订单可以取消");
}
if (order.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("金额异常");
}
order.setStatus("CANCELLED");
orderMapper.update(order);
// 退款逻辑也在这里...
// 库存归还逻辑也在这里...
// 通知逻辑也在这里...
}
}
痛点二:Service 层无限膨胀
随着业务增长,
OrderService可能超过 2000 行,里面混杂着下单、支付、退款、物流、统计等各种逻辑。新人完全不知道从哪里入手。
痛点三:技术与业务语言脱节
java
// ❌ 技术人员写的代码(数据库思维)
orderMapper.updateStatusByIdAndStatus(orderId, "PAID", "PENDING");
userMapper.updateBalance(userId, amount.negate());
// 业务专家说的话:
// "将待支付订单确认支付后,扣减用户账户余额"
// (完全对不上)
痛点四:跨层依赖难以测试
java
// ❌ Service 依赖 Mapper,测试必须 Mock 数据库
@Test
void testCancelOrder() {
// 需要 Mock OrderMapper、UserMapper、InventoryMapper...
// 单元测试成本极高
}
DDD 的核心理念
什么是领域驱动设计?
DDD(Domain-Driven Design) 是 Eric Evans 在 2003 年提出的软件设计方法论:
以业务领域为核心,用代码精确表达业务模型,让软件能"说"业务语言。

DDD 的两大核心设计
| 设计层次 | 关注点 | 主要工具 |
|---|---|---|
| 战略设计 | 系统如何划分?谁和谁在一起? | 限界上下文、通用语言、上下文映射 |
| 战术设计 | 一个上下文内部如何实现? | 实体、值对象、聚合、领域服务、领域事件 |
DDD 四层架构 vs MVC 三层架构
DDD 典型的四层架构如下,领域层是绝对核心,其他层都围绕它展开:

对比两种架构的核心差异:MVC 按技术职责 分层,DDD 按业务价值分层,领域层是绝对核心。

💡 最关键的区别:MVC 的 Service 是业务逻辑的"容器",DDD 的领域层中每个对象都"自己负责自己的业务规则",应用层只做编排。
典型的 DDD 项目结构
与 MVC 按技术职责组织代码不同,DDD 按业务边界 + 分层职责组织,同一个业务概念(如 Order)的所有相关代码都放在同一个上下文内:
com.example.order/ ← 订单限界上下文(独立模块)
├── interfaces/ ← 接口层:对外暴露
│ ├── rest/
│ │ ├── OrderController.java ← HTTP 接口
│ │ └── dto/
│ │ ├── PlaceOrderRequest.java ← 入参 DTO
│ │ └── OrderResponse.java ← 出参 DTO
│ └── event/
│ └── PaymentEventListener.java ← 监听外部 MQ 事件
│
├── application/ ← 应用层:用例编排
│ ├── service/
│ │ └── OrderApplicationService.java ← 编排领域对象,不含 if/else 业务判断
│ └── command/
│ └── PlaceOrderCommand.java ← 用例输入对象
│
├── domain/ ← 领域层:核心业务(零框架依赖)
│ ├── model/
│ │ ├── Order.java ← ★ 聚合根,包含业务行为
│ │ ├── OrderItem.java ← 实体
│ │ └── vo/
│ │ ├── OrderId.java ← 值对象:唯一标识
│ │ ├── Money.java ← 值对象:金额
│ │ └── OrderStatus.java ← 枚举:状态流转规则
│ ├── service/
│ │ └── OrderPricingService.java ← 领域服务:跨聚合的业务规则
│ ├── event/
│ │ └── OrderPlacedEvent.java ← 领域事件(过去时命名)
│ └── repository/
│ └── OrderRepository.java ← 仓储接口(定义在领域层)
│
└── infrastructure/ ← 基础设施层:技术实现细节
├── persistence/
│ ├── OrderRepositoryImpl.java ← 仓储实现(实现领域层接口)
│ ├── mapper/
│ │ └── OrderMapper.java ← MyBatis Mapper
│ └── po/
│ └── OrderPO.java ← 数据库持久化对象
└── acl/
└── InventoryServiceAdapter.java ← 防腐层:隔离库存服务调用
与 MVC 结构的直观对比:
| 对比维度 | MVC 结构 | DDD 结构 |
|---|---|---|
| 组织方式 | 按技术层切分(controller/service/dao) | 按业务边界 + 分层职责 |
| Order 相关代码 | 散落在各层目录下 | 全部收敛在 order/ 上下文模块内 |
| 业务规则位置 | OrderService.java(堆积) |
Order.java(聚合根内部,自我管理) |
| 新人理解业务 | 需要跨多个目录来回跳 | 进入 domain/model/ 即可看清业务全貌 |
| 添加新业务 | 修改多个层的文件 | 先在领域层添加行为,再在应用层编排 |
贫血模型 vs 充血模型
这是 MVC 和 DDD 最直观的代码差异:
MVC:贫血模型(Anemic Domain Model)
java
// ❌ 贫血:对象只有数据,没有行为
@Data
public class Order {
private Long id;
private String status; // "PENDING", "PAID", "CANCELLED"
private BigDecimal totalAmount;
private List<OrderItem> items;
}
// 所有行为都在 Service 中
@Service
public class OrderService {
public void pay(Long orderId, BigDecimal amount) { /* ... */ }
public void cancel(Long orderId) { /* ... */ }
public void addItem(Long orderId, Long productId) { /* ... */ }
}
DDD:充血模型(Rich Domain Model)
java
// ✅ 充血:对象既有数据,也有业务行为
public class Order {
private OrderId id;
private OrderStatus status;
private Money totalAmount;
private List<OrderItem> items;
// ✅ 业务行为在领域对象内部
public void pay(Money amount) {
if (!this.status.canPay()) {
throw new OrderException("当前状态不可支付: " + this.status);
}
if (!amount.equals(this.totalAmount)) {
throw new OrderException("支付金额与订单金额不符");
}
this.status = OrderStatus.PAID;
// 发布领域事件
DomainEvents.publish(new OrderPaidEvent(this.id, amount));
}
public void cancel(String reason) {
if (!this.status.canCancel()) {
throw new OrderException("当前状态不可取消: " + this.status);
}
this.status = OrderStatus.CANCELLED;
DomainEvents.publish(new OrderCancelledEvent(this.id, reason));
}
public void addItem(Product product, int quantity) {
this.items.stream()
.filter(item -> item.getProductId().equals(product.getId()))
.findFirst()
.ifPresentOrElse(
item -> item.increaseQuantity(quantity),
() -> items.add(OrderItem.create(product, quantity))
);
this.totalAmount = calculateTotal();
}
}
思维转换对照

核心思维转换清单
| 问题 | MVC 思维 | DDD 思维 |
|---|---|---|
| 从哪里开始设计? | 数据库表 | 业务领域 |
| 业务逻辑放在哪里? | Service 层 | 领域对象内部 |
| 如何命名? | updateOrderStatus() |
order.pay() / order.cancel() |
| 如何划分模块? | 按技术层(controller/service/dao) | 按业务边界(订单/用户/库存) |
| 对象有哪些? | 基本等于数据库表 | 实体、值对象、聚合、服务... |
| 如何处理跨模块调用? | Service 互相注入 | 通过领域事件解耦 |
代码对比示例
场景:用户下单
MVC 写法:
java
@Service
@Transactional
public class OrderService {
@Autowired private OrderMapper orderMapper;
@Autowired private ProductMapper productMapper;
@Autowired private UserMapper userMapper;
@Autowired private InventoryMapper inventoryMapper;
public Long createOrder(Long userId, Long productId, int quantity) {
// 1. 查用户
UserDO user = userMapper.findById(userId);
if (user == null) throw new RuntimeException("用户不存在");
// 2. 查商品
ProductDO product = productMapper.findById(productId);
if (product == null) throw new RuntimeException("商品不存在");
if (!"ON_SALE".equals(product.getStatus())) throw new RuntimeException("商品已下架");
// 3. 检查库存
InventoryDO inventory = inventoryMapper.findByProductId(productId);
if (inventory.getStock() < quantity) throw new RuntimeException("库存不足");
// 4. 计算金额
BigDecimal totalAmount = product.getPrice().multiply(BigDecimal.valueOf(quantity));
// 5. 创建订单 DO
OrderDO order = new OrderDO();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setTotalAmount(totalAmount);
order.setStatus("PENDING");
order.setCreateTime(LocalDateTime.now());
orderMapper.insert(order);
// 6. 扣减库存
inventoryMapper.decreaseStock(productId, quantity);
return order.getId();
}
}
DDD 写法:
java
// ✅ 应用服务:只负责协调,不含业务逻辑
@ApplicationService
@Transactional
public class PlaceOrderCommandHandler {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public OrderId handle(PlaceOrderCommand cmd) {
// 1. 加载聚合
Product product = productRepository.findById(cmd.getProductId())
.orElseThrow(() -> new ProductNotFoundException(cmd.getProductId()));
// 2. 创建订单聚合(业务规则在领域对象内部)
Order order = Order.place(
OrderId.generate(),
cmd.getUserId(),
product,
cmd.getQuantity()
);
// 3. 持久化
orderRepository.save(order);
return order.getId();
}
}
// ✅ 领域对象:包含业务规则
public class Order {
// 工厂方法,包含所有创建规则
public static Order place(OrderId id, UserId userId, Product product, int quantity) {
product.ensureOnSale(); // 商品状态校验(在 Product 内部)
product.reserveStock(quantity); // 占用库存(在 Product 内部)
Money totalAmount = product.getPrice().multiply(quantity);
Order order = new Order(id, userId, OrderStatus.PENDING, totalAmount);
order.addItem(product, quantity);
// 发布领域事件
order.publishEvent(new OrderPlacedEvent(id, userId, totalAmount));
return order;
}
}
什么时候用 DDD
⚠️ DDD 不是银弹,引入它是有成本的。

推荐使用 DDD 的场景
| ✅ 适合 DDD | ❌ 不适合 DDD |
|---|---|
| 复杂的电商系统(订单、库存、促销、支付) | 简单后台管理系统 |
| 金融系统(账户、交易、风控) | 数据报表平台 |
| 供应链系统(采购、仓储、物流) | 纯 CRUD 的配置管理系统 |
| 需要长期演进的核心业务系统 | 需要快速交付的小项目 |
| 微服务架构需要划分边界 | 业务逻辑较少的中台服务 |