深入理解DDD(领域驱动设计)架构设计
深入理解DDD(领域驱动设计)架构设计
一、什么是DDD?
领域驱动设计(Domain-Driven Design,简称DDD) 是由Eric Evans在其2003年出版的经典著作《领域驱动设计:软件核心复杂性应对之道》中提出的一套软件设计方法论。DDD的核心思想是:将业务领域的知识和逻辑放在软件设计的中心位置,通过建立与业务专家共享的通用语言(Ubiquitous Language),让软件模型真正反映业务需求。
核心观点: 软件的复杂性不在于技术,而在于业务领域本身。DDD提供了一套应对复杂业务领域的设计方法论。
二、为什么需要DDD?
传统软件开发中常见的问题:
- 技术主导 - 架构设计以技术分层为中心,业务逻辑散落在各个技术组件中
- 沟通障碍 - 业务专家和技术人员使用不同的语言,导致需求理解偏差
- 复杂性失控 - 随着系统演进,业务逻辑交织混乱,难以维护和扩展
- 边界模糊 - 缺乏清晰的模块边界划分,系统耦合度高
DDD通过以下方式解决上述问题:
- 以领域模型为核心,而非技术框架
- 建立通用语言(Ubiquitous Language),统一业务和技术视角
- 通过限界上下文(Bounded Context)划分清晰边界
- 提供战术设计模式,指导复杂业务逻辑的实现
三、DDD的战略设计
战略设计解决的是大型系统的宏观架构问题,主要包括:
1. 限界上下文(Bounded Context)
限界上下文是DDD最核心的概念之一。它是一个明确的业务边界,在这个边界内部,领域模型具有统一的含义和规则。
- 每个限界上下文拥有自己独立的通用语言
- 不同的上下文之间通过上下文映射(Context Map)进行集成
- 一个大型系统由多个限界上下文组成,每个上下文可以独立演进
2. 通用语言(Ubiquitous Language)
通用语言是业务专家和开发团队共同创建的一套统一词汇表,确保所有人对业务概念的理解一致。
- 体现在代码中的类名、方法名、变量名中
- 体现在需求文档和设计文档中
- 通过持续沟通不断迭代完善
3. 子域(Subdomain)
根据业务价值的不同,领域可以分为三类子域:
| 类型 | 特点 | 策略 |
|---|---|---|
| 核心域 | 核心竞争力,最复杂的业务逻辑 | 重点投入,自主研发 |
| 支撑子域 | 支持核心业务,但非核心 | 可自研或外包 |
| 通用子域 | 通用功能,如认证、通知 | 优先使用现成方案 |
4. 上下文映射(Context Map)
定义了不同限界上下文之间的集成关系,常见的关系模式包括:
- 合作关系 - 两个团队协同演进
- 共享内核 - 共享部分模型
- 客户-供应商 - 上游影响下游
- 防腐层 - 隔离外部系统的变化
四、DDD的战术设计
战术设计关注的是微观层面的实现模式,包括以下核心构建块:
1. 实体(Entity)
- 有唯一标识(Id)的对象
- 具有生命周期,其状态可以改变
- 通过ID进行区分,而非属性值
- 示例:用户(User)、订单(Order)、产品(Product)
public class Order {
private String orderId; // 唯一标识
private OrderStatus status;
private List<OrderItem> items;
public void pay() {
// 业务逻辑:修改订单状态
this.status = OrderStatus.PAID;
// 触发领域事件
DomainEventBus.publish(new OrderPaidEvent(this.orderId));
}
}
2. 值对象(Value Object)
- 没有唯一标识,通过属性值来定义
- 不可变(Immutable)
- 可以共享和复用
- 示例:金额(Money)、地址(Address)、颜色(Color)
public class Money {
private final BigDecimal amount;
private final String currency;
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不一致");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}
3. 聚合(Aggregate)
- 一组相关对象的集合,作为数据修改的一致性边界
- 聚合根(Aggregate Root)是外部访问的唯一入口
- 聚合内保证强一致性,聚合间保证最终一致性
// 订单聚合
public class Order { // 聚合根
private String orderId;
private List<OrderItem> items; // 聚合内部
private Money totalAmount;
public void addItem(Product product, int quantity) {
// 业务规则:检查库存、计算金额
OrderItem item = new OrderItem(product, quantity);
this.items.add(item);
this.totalAmount = this.totalAmount.add(item.getSubtotal());
}
}
4. 领域服务(Domain Service)
- 处理不适合放在实体或值对象中的业务逻辑
- 通常是涉及多个聚合的协调操作
- 无状态(Stateless)
public class TransferService {
public void transfer(Account from, Account to, Money amount) {
from.withdraw(amount);
to.deposit(amount);
// 记录转账日志
}
}
5. 领域事件(Domain Event)
- 领域中发生的、业务人员关心的重要事件
- 用于实现聚合间的最终一致性
- 通常以过去时命名:OrderPaidEvent、UserRegisteredEvent
6. 仓储(Repository)
- 提供聚合的持久化和检索接口
- 对上层屏蔽数据存储的细节
- 每个聚合根对应一个仓储
public interface OrderRepository {
Order findById(String orderId);
void save(Order order);
void delete(Order order);
}
7. 工厂(Factory)
- 封装复杂对象的创建逻辑
- 当构造过程比简单的构造函数更复杂时使用
五、经典DDD分层架构
DDD推荐经典的四层架构:
┌─────────────────────────────┐
│ 用户接口层 (User Interface) │ - Controller、DTO、View
├─────────────────────────────┤
│ 应用层 (Application) │ - Application Service、DTO组装
├─────────────────────────────┤
│ 领域层 (Domain) │ - Entity、Value Object、Aggregate
│ │ - Domain Service、Domain Event
│ │ - Repository接口
├─────────────────────────────┤
│ 基础设施层 (Infrastructure) │ - Repository实现、DB、MQ、Cache
└─────────────────────────────┘
各层职责详解
1. 用户接口层
- 负责接收用户请求和响应
- 包括REST API接口、RPC接口、消息监听器等
- 完成DTO与领域对象的转换
- 不包含任何业务逻辑
2. 应用层
- 协调领域对象的操作,编排业务流程
- 处理事务、权限校验、日志记录等横切关注点
- 应用服务(Application Service)是这一层的主要组件
- 业务逻辑应下沉到领域层,应用层仅做协调
3. 领域层
- 系统的核心层,包含所有业务逻辑
- 实体、值对象、聚合、领域服务、领域事件都在此层
- 仓储接口在此层定义
- 不依赖任何其他层或外部框架
4. 基础设施层
- 提供技术支持:数据库访问、消息队列、缓存、文件系统等
- 实现领域层定义的仓储接口
- 通过依赖注入(DI)将具体实现注入到上层
六、DDD与传统分层架构的对比
| 维度 | 传统架构(如MVC) | DDD架构 |
|---|---|---|
| 核心关注 | 技术分层 | 业务领域 |
| 业务逻辑 | 散落在Service层 | 集中在领域层 |
| 模型设计 | 贫血模型(数据+getter/setter) | 富血模型(数据+行为) |
| 边界划分 | 技术边界(Controller/Service/DAO) | 业务边界(限界上下文) |
| 应对变化 | 技术变化影响业务代码 | 业务变化隔离在领域层内 |
| 团队协作 | 按技术角色分工 | 按业务领域组织团队 |
七、什么时候使用DDD?
DDD适合的场景:
✅ 复杂业务系统 - 如电商、金融、医疗、ERP等
✅ 长期演进的项目 - 需要持续迭代和维护
✅ 大型团队协作 - 需要清晰的边界划分
✅ 业务变化频繁 - 需要快速响应业务需求
DDD不太适合的场景:
❌ 简单的CRUD系统 - 如简单的信息管理后台
❌ 原型验证阶段 - 快速试错为主,不需要复杂的架构
❌ 小团队短工期项目 - DDD的学习和实施成本较高
八、落地DDD的建议
- 从战略设计开始 - 先梳理业务边界,再考虑技术实现
- 与业务专家紧密协作 - 共同建立通用语言
- 持续重构 - 领域模型不是一次性设计出来的,而是持续演进的
- 结合微服务 - DDD的限界上下文天然适合微服务拆分
- 采用整洁架构 - DDD + 整洁架构/CQRS/事件溯源可以更好地应对复杂系统
- 工具选型 - 可以使用JPA、MyBatis等ORM框架,但要注意不要被技术框架绑架
九、常见误区
| 误区 | 正确理解 |
|---|---|
| DDD = 分层架构 | DDD的核心是领域建模,分层只是实现方式之一 |
| 必须用DDD的所有模式 | 根据实际业务复杂性,选择性使用 |
| DDD=微服务 | DDD可以指导微服务拆分,但大单体也可以使用DDD |
| 领域对象必须充血 | 简单对象可以保持贫血,关键是业务复杂的地方才用充血模型 |
十、总结
DDD不仅仅是一套架构模式,更是一种以业务为核心的软件设计哲学。它通过战略设计解决系统的宏观边界划分问题,通过战术设计提供微观层面的实现指导。
在实践中,建议根据业务复杂度渐进式地引入DDD,不必一开始就追求完美的领域模型。重点是要建立业务和技术的共同语言,让代码真正反映业务逻辑,这才是DDD的精髓所在。
参考书籍:《领域驱动设计:软件核心复杂性应对之道》- Eric Evans
《实现领域驱动设计》- Vaughn Vernon
浙公网安备 33010602011771号