DDD架构——充血模型、领域模型
文章目录
- DDD的概念
- 充血模型
- 介绍
- 贫血模型示例
- 充血模型示例
- 总结
DDD的概念
DDD(Domain-driven design),即领域驱动设计,是一种软件设计方法。也就是说 DDD 是指导我们做软件工程设计的一种手段,它提供了用切割工程模型的各类技巧。它的核心是将业务复杂性问题作为设计工作的核心,通过建立有效的领域模型来应对复杂性。
💪DDD的两大支柱: 战略设计 & 战术设计 战略设计解决“做什么”和“如何划分”的问题,战术设计解决“怎么做”的编码实现问题。
- 战略设计:主要以应对复杂的业务需求,通过抽象、分治的过程,合理的拆分为独立的多个微服务,从而分而治之。
- 战术设计:在这个范畴下,主要以讨论如何基于面向对象思维,运用领域模型来表达业务概念(可以理解为设计模式)。
简单来说,DDD 就像一位城市规划师。战略设计是划分城市的功能区(住宅区、商业区、工业区),并定义它们之间的道路(上下文映射)。战术设计则是在每个功能区(如住宅区)内,规定房屋(实体)、街道(值对象)、小区(聚合)应该如何建设,以及物业(领域服务)的职责是什么。这样,整个城市才能井井有条,高效运转。
充血模型
介绍
充血模型是指将数据和操作这些数据的业务逻辑封装在同一个领域对象中的设计方式。
优势:
- 这样的方式可以在使用一个对象时,就顺便拿到这个对象的提供的一系列方法信息,所有使用对象的逻辑方法,都不需要自己再次处理同类逻辑。
- 但不要只是把充血模型,仅限于一个类的设计和一个类内的方法设计。充血还可以是整个包结构,一个包下包括了用于实现此包 Service 服务所需的各类零部件(模型、仓储、工厂),也可以被看做充血模型。
- 同时我们还会再一个同类的类下,提供对应的内部类,如用户实名,包括了,通信类、实名卡、银行卡、四要素等。它们都被写进到一个用户类下的内部子类,这样在代码编写中也会清晰的看到子类的所属信息,更容易理解代码逻辑,也便于维护迭代
贫血模型示例
// 只有数据,没有行为 - 纯粹的数据容器
public class Order {private Long id;private BigDecimal amount;private String status;private List<OrderItem> items;// 只有getter/setter,没有业务方法public BigDecimal getAmount() { return amount; }public void setAmount(BigDecimal amount) { this.amount = amount; }public String getStatus() { return status; }public void setStatus(String status) { this.status = status; }
}// 业务逻辑都分散在Service中
@Service
public class OrderService {public void placeOrder(Order order) {// 验证逻辑if (order.getAmount() == null || order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {throw new IllegalArgumentException("订单金额必须大于0");}// 状态设置逻辑order.setStatus("PENDING");// 保存逻辑orderRepository.save(order);}
}
充血模型示例
// 富含业务行为的领域对象
public class Order {private Long id;private BigDecimal amount;private OrderStatus status;private List<OrderItem> items;// 构造函数封装创建逻辑public Order(BigDecimal amount, List<OrderItem> items) {if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {throw new IllegalArgumentException("订单金额必须大于0");}this.amount = amount;this.items = new ArrayList<>(items);this.status = OrderStatus.PENDING;}// 业务方法:下单public void place() {validateOrder(); // 内部验证this.status = OrderStatus.CONFIRMED;// 可以在这里发布领域事件:new OrderPlacedEvent(this)}// 业务方法:取消订单public void cancel() {if (this.status != OrderStatus.PENDING && this.status != OrderStatus.CONFIRMED) {throw new IllegalStateException("当前状态不能取消订单");}this.status = OrderStatus.CANCELLED;}// 业务方法:计算总价public BigDecimal calculateTotal() {return items.stream().map(OrderItem::getSubtotal).reduce(BigDecimal.ZERO, BigDecimal::add);}// 内部验证方法private void validateOrder() {if (items == null || items.isEmpty()) {throw new IllegalStateException("订单必须包含商品");}}// getter方法(没有setter,通过业务方法修改状态)public Long getId() { return id; }public BigDecimal getAmount() { return amount; }public OrderStatus getStatus() { return status; }
}
总结
🖊️总结: 充血模型是DDD战术设计的灵魂,它让领域对象从被动的"数据袋子"变成主动的、有行为的业务实体。这种设计方式让代码更符合面向对象思想,也更容易维护和演进。
领域模型有如下概念:
- 领域服务
- 领域对象
- 仓储定义
- 事件消息
- 端口适配器
⭐⭐⭐
领域模型还有一个特点,它自身只关注业务功能实现,不与外部任何接口和服务直连。如;不会直接调用 DAO 操作库,也不会调用缓存操作 Redis,更不会直接引入 RPC 连接其他微服务。而是通过仓库和端口适配器,定义调用外部数据的含有出入参对象的接口标准,让基础设施层做具体的调用实现——通过这样的方式让领域只关心业务实现,同时做好防腐。
领域模型层次:
- 实体 (有身份,有生命周期)
- 值对象 (无身份,描述性)
↓- 聚合 (一致性边界)
├── 聚合根 (入口点)
└── 聚合内部对象
↓- 限界上下文 (业务边界)
├── 领域服务 (跨聚合逻辑)
├── 领域事件 (业务事实)
└── 仓储接口 (持久化抽象)