DDD领域驱动与传统CRUD
DDD 是一套 应对复杂业务系统 的设计方法论,核心是 让代码直接映射业务逻辑,避免技术实现与业务需求脱节。
关键区别:
- 传统开发:根据数据库表写 CRUD(技术驱动)。
- DDD:根据业务行为建模(业务驱动)。
举例:
- 传统方式:设计
orders
表 → 写OrderDAO
→ 实现增删改查。 - DDD 方式:先分析业务如何描述“订单”(如“下单”、“取消”、“支付超时”)→ 再建模为
Order
对象的行为。
贫血模型与充血模型
本质特征
特征 | 贫血模型 | 充血模型 |
---|---|---|
业务逻辑存放位置 | Service层 | 领域对象(Entity/Value Object)内部 |
领域对象职责 | 仅作为数据载体(DTO) | 封装数据和行为 |
代码表现 | 大量Getter/Setter,无业务方法 | 包含业务方法 |
适用场景 | 简单CRUD系统 | 复杂业务系统(DDD推荐) |
代码实现对比
贫血模型实现
Order
只是数据的容器,业务逻辑散落在Service
中。- 当业务复杂时,
Service
会变成“上帝类”(God Class)。
// 1. 贫血的Order类(仅有数据)
public class Order {private String orderId;private String productId;private int quantity;private String status;// 只有Getter/Setter,无业务逻辑public String getOrderId() { return orderId; }public void setOrderId(String orderId) { this.orderId = orderId; }// 其他Getter/Setter省略...
}// 2. 业务逻辑全在Service中
@Service
public class OrderService {public void createOrder(String productId, int quantity) {Order order = new Order();order.setOrderId(UUID.randomUUID().toString());order.setProductId(productId);order.setQuantity(quantity);order.setStatus("CREATED");// 校验逻辑也在Service中if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}orderRepository.save(order);}
}
充血模型实现
Order
自己负责业务规则(如创建校验、状态转换)。- 业务逻辑高内聚,Service层变薄。
// 1. 充血的Order类(封装数据和行为)
public class Order {private String orderId;private String productId;private int quantity;private OrderStatus status; // 枚举// 私有构造方法,强制通过工厂方法创建private Order(String productId, int quantity) {this.orderId = UUID.randomUUID().toString();this.productId = productId;this.quantity = quantity;this.status = OrderStatus.CREATED;}// 静态工厂方法(封装创建逻辑)public static Order create(String productId, int quantity) {if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}return new Order(productId, quantity);}// 领域行为public void cancel() {if (this.status == OrderStatus.PAID) {throw new IllegalStateException("Paid order cannot be cancelled");}this.status = OrderStatus.CANCELLED;}
}// 2. 应用层Service(仅协调流程)
@Service
public class OrderAppService {public void createOrder(String productId, int quantity) {Order order = Order.create(productId, quantity); // 调用领域对象orderRepository.save(order);}
}
DDD实战
架构图
架构设计
src/
├── presentation/ # 用户接口层
├── application/ # 应用层
├── domain/ # 领域层
│ ├── model/ # 领域模型(实体、值对象等)
│ └── repository/ # 仓储接口
└── infrastructure/ # 基础设施层(简单实现)
代码示例
(1) 用户接口层(Presentation Layer)
处理HTTP请求,接收用户输入。
// presentation/OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {private final OrderAppService orderAppService; // 依赖应用层@PostMappingpublic ResponseEntity<String> createOrder(@RequestBody CreateOrderRequest request) {orderAppService.createOrder(request.toCommand()); // 转换为命令对象return ResponseEntity.ok("Order created");}
}// DTO:用户请求参数
public class CreateOrderRequest {private String productId;private int quantity;// 转换为应用层命令对象public CreateOrderCommand toCommand() {return new CreateOrderCommand(productId, quantity);}
}
(2) 应用层(Application Layer)
协调领域对象完成用例,不包含业务逻辑。
// application/OrderAppService.java
@Service
public class OrderAppService {private final OrderRepository orderRepository; // 依赖领域层仓储public void createOrder(CreateOrderCommand command) {// 1. 调用领域层创建订单Order order = Order.create(command.getProductId(), command.getQuantity());// 2. 通过仓储保存聚合根orderRepository.save(order);// 3. 可发布领域事件(此处省略)}
}// 应用层命令对象
public class CreateOrderCommand {private String productId;private int quantity;// 构造方法、getter省略...
}
(3) 领域层(Domain Layer)
核心业务逻辑,包含实体、值对象、仓储接口等。
3.1 实体(Entity)与聚合根
// domain/model/Order.java
public class Order {private String orderId; // 唯一标识private String productId;private int quantity;private OrderStatus status; // 枚举:CREATED, PAID, CANCELLED等// 私有构造方法,强制通过工厂方法创建private Order(String productId, int quantity) {this.orderId = UUID.randomUUID().toString();this.productId = productId;this.quantity = quantity;this.status = OrderStatus.CREATED;}// 工厂方法(封装创建逻辑)public static Order create(String productId, int quantity) {if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}return new Order(productId, quantity);}// 领域行为public void cancel() {this.status = OrderStatus.CANCELLED;}
}
3.2 仓储接口(Repository)
// domain/repository/OrderRepository.java
public interface OrderRepository {void save(Order order);// 其他查询方法省略...
}
(4) 基础设施层(Infrastructure Layer)
实现领域层定义的仓储接口(如数据库操作)。
// infrastructure/persistence/OrderRepositoryImpl.java
@Repository
public class OrderRepositoryImpl implements OrderRepository {// 模拟数据库(实际可用JPA/MyBatis等)private Map<String, Order> orders = new HashMap<>();@Overridepublic void save(Order order) {orders.put(order.getOrderId(), order);System.out.println("Order saved: " + order.getOrderId());}
}
调用流程图
对比传统CRUD
DDD | 传统CRUD |
---|---|
先设计Order 的行为方法 | 直接操作orders 表 |
业务逻辑在领域层 | 业务逻辑散落在Service中 |
通过聚合根保证一致性 | 需手动校验外键约束 |
充血模型特点
- 禁止Setter:通过构造方法或工厂方法创建对象。
- 领域方法命名:使用业务语言(如
cancel()
而非setStatus("CANCELLED")
)。 - 避免“贫血”的充血模型:
- 错误示例:在
Order
中加了方法,但核心逻辑仍在Service中。
- 错误示例:在
常见误区
- ❌ 认为充血模型等于“把所有代码塞进Entity”:
- 跨聚合逻辑应放在领域服务(Domain Service)中。
- ❌ 忽视聚合根的一致性边界:
- 例如订单和库存属于不同聚合,不能直接在
Order
中修改库存,应通过领域事件解耦。
- 例如订单和库存属于不同聚合,不能直接在