一 领域建模
- 领域建模(Domain Modeling)是领域驱动设计(DDD, Domain-Driven Design)中的核心实践,它是通过建立模型来反映业务领域知识的过程。简单来说,就是把你业务中最重要、最核心的部分用代码和图表清晰地表达出来。
- 为什么需要领域建模?
- 统一语言:让业务人员和技术人员说"同一种语言"
- 清晰边界:明确系统应该做什么,不应该做什么
- 指导实现:为后续的代码实现提供清晰蓝图
二 领域建模的核心概念
2.1 实体(Entity)
- 定义:具有唯一标识的对象,其身份标识在整个生命周期中保持不变,即使属性发生变化。
- 特点:通过ID而非属性来区分、具有生命周期、可变(属性可以改变)
- 案例:
// 电商系统中的用户实体
public class User {
private Long id; // 唯一标识
private String name;
private String email;
// 行为方法
public void changeEmail(String newEmail) {
this.email = newEmail;
}
}
2.2 值对象(Value Object)
- 定义:没有唯一标识的对象,通过属性值来区分。
特点:不可变(创建后属性不能改变)、无生命周期、通过属性值而非ID来比较 - 案例:
// 地址值对象
public class Address {
private final String province;
private final String city;
private final String detail;
public Address(String province, String city, String detail) {
this.province = province;
this.city = city;
this.detail = detail;
}
// 无setter方法,体现不可变性
}
2.3 聚合根(Aggregate Root)
- 定义:一组相关对象的集合,作为数据修改的入口点。
- 特点:外部只能通过聚合根访问内部对象、保证聚合内的一致性、有明确的边界
- 案例:
// 订单聚合,订单是聚合根
public class Order {
private Long id;
private List<OrderItem> items;
private OrderStatus status;
// 添加订单项的方法
public void addItem(Product product, int quantity) {
// 业务规则校验
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("订单已确认,不能修改");
}
items.add(new OrderItem(product, quantity));
}
}
2.4 领域服务(Domain Service)
- 定义:当某个操作不适合放在实体或值对象中时,使用领域服务来封装领域逻辑。
- 特点:无状态、表示领域概念、通常操作多个聚合
- 案例:
// 转账领域服务
public class TransferService {
public void transfer(Account from, Account to, Money amount) {
// 业务逻辑
from.debit(amount);
to.credit(amount);
// 记录交易历史等
}
}
2.5 仓储(Repository)
- 定义:提供聚合根的持久化和检索机制。
- 特点:每个聚合根对应一个仓储、隐藏持久化细节、提供集合式的接口
- 案例:
public interface OrderRepository {
Order findById(Long id);
void save(Order order);
void delete(Order order);
}
三 实体管理关系详解
- 在DDD中,实体之间的关系需要谨慎处理,以下是常见的几种关系:
3.1 一对一关系
- 案例:用户和用户详情
public class User {
private Long id;
private UserDetail detail; // 一对一
}
public class UserDetail {
private String realName;
private String idCard;
}
3.2 一对多关系
- 案例:订单和订单项
public class Order {
private Long id;
private List<OrderItem> items; // 一对多
}
public class OrderItem {
private Product product;
private int quantity;
}
3.3 多对多关系
- 案例:学生和课程
public class Student {
private Long id;
private Set<Course> courses; // 多对多
}
public class Course {
private Long id;
private Set<Student> students; // 反向多对多
}
3.4 重要原则:
-
尽量使用ID引用而非对象引用:减少聚合间的直接耦合
public class Order { private Long customerId; // 使用ID引用客户 // 而不是 private Customer customer; }
-
避免大聚合:保持聚合小巧,提高性能
-
跨聚合修改通过领域事件:保证一致性
四 领域建模实战案例:电商系统
- 让我们通过一个电商系统的例子来实践领域建模:
4.1 识别核心领域
电商核心领域可能包括:
- 商品管理
- 订单处理
- 支付
- 物流
4.2 关键聚合设计
- 商品聚合:
// 聚合根
public class Product {
private Long id; // 商品号
private String name; //商品名
private Price price; // 值对象 价格
private Inventory inventory; // 值对象 库存
public void changePrice(BigDecimal newPrice) {
this.price = new Price(newPrice);
}
}
- 订单聚合:
public class Order {
private Long id;
private Long customerId;
private List<OrderItem> items;
private OrderStatus status;
public void confirm() {
// 业务规则校验
if (items.isEmpty()) {
throw new IllegalStateException("订单不能为空");
}
this.status = OrderStatus.CONFIRMED;
}
}
4.3 领域服务示例
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
public void placeOrder(Customer customer, List<Product> products) {
// 检查库存
if (!inventoryService.checkStock(products)) {
throw new BusinessException("库存不足");
}
// 创建订单
Order order = new Order(customer.getId(), products);
orderRepository.save(order);
// 扣减库存
inventoryService.reduceStock(products);
}
}
五 领域建模的常见误区
- 贫血模型:只有getter/setter,没有业务逻辑
// 反例:贫血模型 public class Order { private Long id; private String status; // 只有getter/setter,没有业务方法 }
- 过度设计:为每个概念都创建类,导致模型过于复杂
- 忽略限界上下文:没有明确划分不同业务边界
- 持久化驱动设计:让数据库表设计主导领域模型
六 领域建模的最佳实践
- 从业务出发:与领域专家密切合作
- 小步迭代:模型随着对业务的理解不断演进
- 使用统一语言:确保业务和技术术语一致
- 保持模型纯净:不混入技术实现细节
- 适当使用工具:如事件风暴(Event Storming)帮助建模