DDD领域驱动设计的8个核心概念
- 一、什么是 DDD?
- 二、8种核心概念全景图
- 三、领域建模基础
- 1. Business Entities (业务实体)
- 2. Model Boundaries (模型边界)
- 3. Aggregation (聚合)
- 4. Entities vs. Value Objects (实体 vs 值对象)
- 四、领域操作
- 5. Operational Modeling (操作建模)
- 6. Layering the Architecture (分层架构)
- 五、构建领域模型
- 7. Build the Domain Model (构建领域模型)
- 六、实践建议
- 何时使用 DDD?
- DDD 的代价
- 建议的实践路径
- 七、总结
为什么你的代码总是改一处、崩一片?因为缺少"领域语言"。
Eric Evans 在 2003 年提出了领域驱动设计(Domain-Driven Design,简称 DDD),它不是一种技术,而是一种通过领域建模来驱动软件设计的方法论。
今天,我们来深入理解 DDD 的 8 个核心概念。
一、什么是 DDD?
核心思想:软件的本质是对现实世界的数字化建模
现实世界 软件世界
───────── ─────────
用户下单 → Order.create()
商品库存 → Product.reduceStock()
支付成功 → Payment.confirm()
问题来了:如果开发者和业务专家说的不是同一种语言?
开发者:这个 Order 实体的 status 字段改成 2
业务:订单"确认"了?那要通知仓库发货啊!
😱 理解偏差,导致系统bug
DDD 的解决方案:统一语言(Ubiquitous Language)
- 开发者、业务专家、产品经理使用相同的术语
- 代码直接反映业务概念
- 减少"翻译"带来的信息丢失
二、8种核心概念全景图

三、领域建模基础
1. Business Entities (业务实体)
核心思想:用模型表达业务概念和知识
什么是实体?
实体是具有唯一标识(ID)的事物,通过ID而非属性来区分。
// ❌ 只关注属性,不是领域思维
public class User {
private String name;
private int age;
private String email;
}
// ✅ 关注身份的实体
@Entity
public class User {
private UserId id; // 唯一标识,核心!
private String name;
private int age;
private String email;
// 即使名字改成"张三",这还是同一个人
// 因为 ID 没变
}
真实案例:电商订单
// 订单实体
@Entity
public class Order {
private OrderId id; // 订单ID
private CustomerId customerId;
private List<OrderItem> items;
private OrderStatus status;
private Money totalAmount;
// 领域行为(业务逻辑)
public void pay(PaymentMethod method) {
if (this.status != OrderStatus.PENDING) {
throw new OrderAlreadyPaidException();
}
this.status = OrderStatus.PAID;
// 发布领域事件
DomainEvents.publish(new OrderPaidEvent(this.id));
}
public void ship(Address shippingAddress) {
if (this.status != OrderStatus.PAID) {
throw new OrderNotPaidException();
}
this.status = OrderStatus.SHIPPED;
DomainEvents.publish(new OrderShippedEvent(this.id));
}
}
关键要点:
- ✅ 实体有唯一的 ID
- ✅ 实体封装业务行为(不只是数据容器)
- ✅ 实体有生命周期(创建→修改→删除)
2. Model Boundaries (模型边界)
核心思想:用边界来隔离复杂度
为什么需要边界?
想象一个巨型电商系统,如果所有东西都混在一起:
UserService 依赖 OrderService
OrderService 依赖 PaymentService
PaymentService 依赖 InventoryService
InventoryService 依赖 UserService
😱 循环依赖,牵一发而动全身
DDD 的解决方案:限界上下文 (Bounded Context)
销售上下文 库存上下文 物流上下文
───────── ───────── ─────────
订单 (Order) 库存 (Stock) 运单 (Shipment)
客户 (Customer) 商品 (Product) 包裹 (Package)
每个上下文有独立的模型和数据!
实践案例:用户的两种身份
// 销售上下文中的"客户"
@Entity
public class Customer {
private CustomerId id;
private String name;
private String shippingAddress;
private VIPLevel level; // VIP等级
}
// 营销上下文中的"用户"
@Entity
public class User {
private UserId id;
private String email;
private List<Tag> interests; // 兴趣标签
private Date lastLoginTime;
}
// 它们是不同的概念,有独立的数据库表
边界的好处:
- ✅ 团队可以并行开发(销售团队、库存团队互不干扰)
- ✅ 微服务拆分的自然边界
- ✅ 降低认知负担(每个上下文关注自己的业务)
3. Aggregation (聚合)
核心思想:把相关对象当作一个单元来管理
问题场景:购物车的一致性
// ❌ 没有聚合概念
class Cart {
List<CartItem> items;
}
class CartItem {
Product product;
int quantity;
}
// 问题:谁能保证下面的操作是原子的?
cart.getItems().add(item1);
cart.getItems().add(item2);
cart.getTotalAmount(); // 可能还没计算完!
聚合根 (Aggregate Root) 来拯救:
// ✅ 使用聚合
public class Cart { // 聚合根
private CartId id;
private List<CartItem> items;
private Money totalAmount;
// 只能通过聚合根修改内部对象
public void addItem(Product product, int quantity) {
CartItem item = findItem(product);
if (item != null) {
item.increaseQuantity(quantity);
} else {
items.add(new CartItem(product, quantity));
}
updateTotalAmount();
}
public void removeItem(ProductId productId) {
items.removeIf(item -> item.getProductId().equals(productId));
updateTotalAmount();
}
// 内部方法,外部不能直接调用
private void updateTotalAmount() {
this.totalAmount = items.stream()
.map(CartItem::getSubTotal)
.reduce(Money.ZERO, Money::add);
}
}
// 外界只能通过聚合根操作
cart.addItem(product, 2); // ✅ 保证一致性
cart.getItems().clear(); // ❌ 编译器不允许!
聚合的设计原则:
1. 只通过聚合根访问内部对象
2. 聚合之间通过ID引用,而非对象引用
3. 一次事务只修改一个聚合
4. Entities vs. Value Objects (实体 vs 值对象)
核心区别:有没有身份标识
对比表:
| 维度 | 实体 (Entity) | 值对象 (Value Object) |
|---|---|---|
| 标识 | 有 ID | 无 ID |
| 相等性 | ID 相同即相同 | 属性相同即相同 |
| 可变性 | 可变 | 不可变 |
| 生命周期 | 有(创建→修改→删除) | 附属于实体 |
| 示例 | 订单、用户 | 金额、地址、日期 |
值对象的实践:
// ✅ 金额作为值对象
@ValueObject // 不可变!
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = 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);
}
// 相等性:所有属性相同才相等
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Money)) return false;
Money other = (Money) obj;
return this.amount.equals(other.amount)
&& this.currency.equals(other.currency);
}
}
// 使用
Money price1 = new Money(new BigDecimal("100"), Currency.CNY);
Money price2 = new Money(new BigDecimal("100"), Currency.CNY);
price1.equals(price2); // ✅ true(属性相同)
// ✅ 地址作为值对象
@ValueObject
public class Address {
private final String province;
private final String city;
private final String street;
private final String zipCode;
// 不可变,没有setter
}
为什么要用值对象?
- ✅ 消除"基本类型偏执"(Primitive Obsession)
- ✅ 代码更安全(不可变)
- ✅ 业务逻辑更清晰("100元"比"100"有意义)
四、领域操作
5. Operational Modeling (操作建模)
核心思想:用专门的对象来操作领域模型
问题:谁负责业务逻辑?
// ❌ 贫血模型(Anemic Model)
@Entity
public class Order {
private OrderId id;
private Money totalAmount;
private OrderStatus status;
// 只有getter/setter,没有业务逻辑
}
// 业务逻辑散落在Service里
@Service
public class OrderService {
public void cancelOrder(Order order) {
if (order.getStatus() == OrderStatus.SHIPPED) {
throw new Exception("已发货,不能取消");
}
if (order.getStatus() == OrderStatus.PAID) {
// 退款逻辑
refundService.refund(order.getTotalAmount());
}
order.setStatus(OrderStatus.CANCELLED);
}
}
// 问题:业务逻辑泄露到Service,Order变成数据容器
DDD 的解决方案:领域服务 (Domain Service) + 领域事件
// ✅ 充血模型(Rich Model)
@Entity
public class Order {
private OrderId id;
private Money totalAmount;
private OrderStatus status;
private List<DomainEvent> events = new ArrayList<>();
// 业务逻辑回到实体内部
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new OrderCannotCancelException("已发货,不能取消");
}
OrderStatus previousStatus = this.status;
this.status = OrderStatus.CANCELLED;
// 记录领域事件
this.events.add(new OrderCancelledEvent(this.id, previousStatus));
}
public List<DomainEvent> getUncommittedEvents() {
return Collections.unmodifiableList(events);
}
public void markEventsAsCommitted() {
events.clear();
}
}
// 领域服务处理跨聚合的业务逻辑
@DomainService
public class OrderCancellationService {
private final OrderRepository orderRepository;
private final RefundService refundService;
public void cancelOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId);
order.cancel(); // 核心业务逻辑在实体内
// 处理领域事件
for (DomainEvent event : order.getUncommittedEvents()) {
if (event instanceof OrderCancelledEvent) {
if (((OrderCancelledEvent) event).getPreviousStatus() == OrderStatus.PAID) {
refundService.refund(order.getTotalAmount());
}
}
eventPublisher.publish(event);
}
order.markEventsAsCommitted();
orderRepository.save(order);
}
}
领域事件 (Domain Event) 的好处:
- ✅ 解耦:订单不需要知道退款服务的存在
- ✅ 可追溯:记录了完整的业务流程
- ✅ 易扩展:新增监听器不影响原有逻辑
6. Layering the Architecture (分层架构)
核心思想:用分层来组织复杂的系统
DDD 的四层架构:
┌─────────────────────────────────┐
│ 用户界面层 (UI Layer) │
│ - 展示数据 │
│ - 接收用户输入 │
└──────────────┬──────────────────┘
│ 调用
┌──────────────▼──────────────────┐
│ 应用层 (Application Layer) │
│ - 编排用例 │
│ - 事务边界 │
└──────────────┬──────────────────┘
│ 调用
┌──────────────▼──────────────────┐
│ 领域层 (Domain Layer) │
│ - 核心业务逻辑 │
│ - 实体、值对象、领域服务 │
└──────────────┬──────────────────┘
│ 调用
┌──────────────▼──────────────────┐
│ 基础设施层 (Infrastructure) │
│ - 数据库、外部API、消息队列 │
└─────────────────────────────────┘
代码示例:电商下单
// === 应用层 ===
@Service
public class PlaceOrderApplicationService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final PaymentGateway paymentGateway;
@Transactional
public OrderDTO placeOrder(PlaceOrderCommand command) {
// 1. 获取领域对象
Customer customer = customerRepository.findById(command.getCustomerId());
List<Product> products = productRepository.findByIds(command.getProductIds());
// 2. 执行领域逻辑
Order order = customer.placeOrder(products, command.getShippingAddress());
// 3. 保存
orderRepository.save(order);
// 4. 返回DTO(不是领域对象!)
return OrderDTO.fromDomain(order);
}
}
// === 领域层 ===
@Entity
public class Customer {
public Order placeOrder(List<Product> products, Address address) {
// 核心业务逻辑在领域层
if (!isVIP()) {
throw new NonVIPCustomerException();
}
Order order = new Order(this.id, products, address);
return order;
}
}
// === 基础设施层 ===
@Repository
public class OrderRepositoryImpl implements OrderRepository {
private final EntityManager entityManager;
public void save(Order order) {
// 具体的数据库操作
entityManager.persist(order);
}
}
分层的关键:
- ✅ 依赖方向:上层依赖下层,领域层不依赖任何层
- ✅ 职责清晰:应用层编排,领域层逻辑,基础设施层实现
- ✅ 可替换性:更换数据库只影响基础设施层
五、构建领域模型
7. Build the Domain Model (构建领域模型)
核心思想:从业务知识中提取领域模型
如何从零开始建模?
步骤1:事件风暴 (Event Storming)
场景:用户下单
领域事件:
- OrderPlaced (订单已创建)
- OrderPaid (订单已支付)
- OrderShipped (订单已发货)
- OrderCancelled (订单已取消)
命令:
- PlaceOrder (下单)
- PayOrder (支付)
- ShipOrder (发货)
- CancelOrder (取消)
聚合:
- Order (订单)
- Customer (客户)
- Product (商品)
步骤2:提炼模型
// 初步模型(从事件风暴得到)
public class Order {
void place() { } // 下单
void pay() { } // 支付
void ship() { } // 发货
void cancel() { } // 取消
}
// 细化模型(添加业务规则)
public class Order {
private OrderStatus status;
public void pay(PaymentMethod method) {
// 业务规则:只有待支付订单才能支付
if (this.status != OrderStatus.PENDING) {
throw new InvalidOrderStatusException();
}
// 业务规则:支付金额必须等于订单金额
// ...
this.status = OrderStatus.PAID;
}
public void ship() {
// 业务规则:只有已支付订单才能发货
if (this.status != OrderStatus.PAID) {
throw new OrderNotPaidException();
}
// 业务规则:检查库存
// ...
this.status = OrderStatus.SHIPPED;
}
}
步骤3:验证模型(与业务专家沟通)
开发者:订单支付后可以直接取消吗?
业务专家:不行,如果已经发货就不能取消
开发者:如果是未发货的已支付订单呢?
业务专家:可以取消,但需要退款
开发者:好的,我理解了,会更新模型
六、实践建议
何时使用 DDD?
| 适合场景 | 不适合场景 |
|---|---|
| ✅ 复杂的业务领域 | ❌ 简单的CRUD应用 |
| ✅ 多团队协作的大型项目 | ❌ 小型个人项目 |
| ✅ 业务逻辑经常变化 | ❌ 技术导向的项目 |
| ✅ 需要长期维护的遗留系统重构 | ❌ 快速原型验证 |
DDD 的代价
- ⚠️ 学习曲线陡峭:需要理解大量概念
- ⚠️ 前期投入大:建模需要时间
- ⚠️ 可能过度设计:简单业务硬套DDD
建议的实践路径
- 从小项目开始:先在一个小模块实践DDD
- 与业务专家结对:建立统一语言
- 增量演进:不要试图一次性建模所有内容
- 关注业务价值:DDD的目的是解决业务问题,不是为了技术炫技
七、总结
DDD 的 8 个核心概念:
| 概念 | 作用 |
|---|---|
| Business Entities | 用模型表达业务概念 |
| Model Boundaries | 用边界隔离复杂度 |
| Aggregation | 保证数据一致性 |
| Entities vs Value Objects | 区分有身份和无身份的对象 |
| Operational Modeling | 用领域服务和事件封装操作 |
| Layering | 用分层组织代码结构 |
| Build Domain Model | 从业务知识中提取模型 |
| Unified Language | 让团队讲同一种语言 |
记住:DDD 不是银弹,它是一套让技术团队与业务专家有效沟通的方法论。
真正理解 DDD,你会发现:代码不再是冰冷的机器指令,而是业务逻辑的直接表达。
评论
发表评论
|
|
|