目录
一、充血模型的核心概念
1. 领域对象的职责
2. 领域层的核心地位
二、充血模型的特点
1. 以领域为中心的设计
2. 强封装性
3. 支持复杂业务逻辑
4. 便于领域知识传递
三、充血模型的优势
四、充血模型的实现要点
1. 明确实体与值对象
2. 领域服务的定位
3. 领域事件的应用
4. 仓储模式(Repository)
五、充血模型 vs. 贫血模型
六、充血模型的应用挑战
七、充血模型的典型应用场景
总结
代码示例
在领域驱动设计(DDD)架构中,充血模型(Rich Domain Model)是一种核心设计模式,与贫血模型(Anemic Domain Model)相对。它强调领域对象不仅包含数据(属性),还包含业务逻辑(方法),使领域模型能够直接承载业务规则和操作,更贴近真实世界的业务场景。以下从核心概念、特点、优势、实现要点及与贫血模型的对比等方面展开说明。
优势 |
具体表现 |
逻辑一致性更强 |
业务规则集中在领域对象内,避免同一规则在不同服务中重复实现或不一致。 |
可维护性更高 |
逻辑修改时只需更新对应的领域对象,减少跨层影响,符合“单一职责原则”。 |
领域模型更健壮 |
通过封装状态变更逻辑,防止非法状态(如负数金额)的出现,提升模型的健壮性。 |
测试更便捷 |
领域对象可独立测试(如单元测试),无需依赖上层服务或外部组件。 |
适应业务变化 |
业务规则调整时,只需修改领域对象的方法,符合“开闭原则”。 |
维度 |
充血模型 |
贫血模型 |
领域对象职责 |
包含数据和业务逻辑 |
仅包含数据(get/set方法),逻辑在服务层 |
业务逻辑位置 |
领域层(领域对象内) |
应用层或服务层 |
代码组织 |
领域对象高内聚,层次清晰 |
逻辑分散,服务层臃肿 |
可维护性 |
高(逻辑集中,修改影响范围小) |
低(逻辑分散,易出现“牵一发而动全身”) |
业务复杂度适配 |
适合复杂业务(如金融、电商核心流程) |
适合简单业务或快速原型开发 |
典型场景 |
长期维护的复杂系统 |
轻量级系统或临时项目 |
充血模型是DDD架构的灵魂,它通过“数据与行为绑定”的设计,将业务逻辑回归到领域本身,使系统更贴近真实世界的业务规则。尽管其实现需要一定的领域建模能力和前期投入,但在复杂业务场景下,充血模型能显著提升系统的可维护性、扩展性和业务表达能力,是构建健壮领域模型的关键。
以下是Java版本的订单实体充血模型实现,添加了详细注释说明业务逻辑和领域规则:
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 订单状态枚举
*/
public enum OrderStatus {
CREATED("已创建"),
PAID("已支付"),
SHIPPED("已发货"),
COMPLETED("已完成"),
CANCELLED("已取消");
private final String description;
OrderStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
/**
* 值对象:金额
* 不可变对象,封装货币金额和币种信息
*/
public final class Money {
private final BigDecimal amount;
private final String currency;
public Money(BigDecimal amount, String currency) {
// 防御性编程:禁止创建负金额(领域规则)
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
this.amount = amount;
this.currency = Objects.requireNonNullElse(currency, "CNY");
}
// 金额加法,返回新的Money对象(不可变性)
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不匹配");
}
return new Money(this.amount.add(other.amount), this.currency);
}
// 金额减法,返回新的Money对象
public Money subtract(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不匹配");
}
return new Money(this.amount.subtract(other.amount), this.currency);
}
// 判断金额是否为负
public boolean isNegative() {
return this.amount.compareTo(BigDecimal.ZERO) < 0;
}
// Getter方法(无Setter,保证不可变性)
public BigDecimal getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.equals(money.amount) && currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}
/**
* 值对象:商品
* 不可变对象,描述订单中的商品信息
*/
public final class Product {
private final String id;
private final String name;
private final Money price;
public Product(String id, String name, Money price) {
this.id = Objects.requireNonNull(id, "商品ID不能为空");
this.name = Objects.requireNonNull(name, "商品名称不能为空");
this.price = Objects.requireNonNull(price, "商品价格不能为空");
}
// Getter方法
public String getId() {
return id;
}
public String getName() {
return name;
}
public Money getPrice() {
return price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return id.equals(product.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
/**
* 值对象:订单项
* 不可变对象,描述订单中的一个商品及其数量
*/
public final class OrderItem {
private final Product product;
private final int quantity;
public OrderItem(Product product, int quantity) {
this.product = Objects.requireNonNull(product, "商品不能为空");
if (quantity <= 0) {
throw new IllegalArgumentException("商品数量必须大于0");
}
this.quantity = quantity;
}
// 计算订单项总金额
public Money calculateItemTotal() {
return new Money(
product.getPrice().getAmount().multiply(BigDecimal.valueOf(quantity)),
product.getPrice().getCurrency()
);
}
// Getter方法
public Product getProduct() {
return product;
}
public int getQuantity() {
return quantity;
}
}
/**
* 订单实体(充血模型)
* 封装订单的状态和业务行为,确保业务规则在实体内部完成
*/
public class Order {
private final String id; // 订单ID(不可变)
private final String customerId; // 客户ID(不可变)
private final List items; // 订单项列表
private OrderStatus status; // 订单状态
private Money discount; // 折扣金额
private LocalDateTime paymentTime; // 支付时间
private LocalDateTime shippingTime; // 发货时间
private LocalDateTime completionTime; // 完成时间
private LocalDateTime cancelTime; // 取消时间
// 构造函数:初始化订单
public Order(String id, String customerId, List items) {
this.id = Objects.requireNonNull(id, "订单ID不能为空");
this.customerId = Objects.requireNonNull(customerId, "客户ID不能为空");
// 防御性拷贝:防止外部修改原始列表
this.items = new ArrayList<>(Objects.requireNonNull(items, "订单项列表不能为空"));
if (this.items.isEmpty()) {
throw new IllegalArgumentException("订单必须包含至少一个商品项");
}
this.status = OrderStatus.CREATED;
this.discount = new Money(BigDecimal.ZERO, "CNY");
// 创建时进行订单校验
validateOrderState();
}
// 计算订单总金额(业务逻辑)
public Money calculateTotalAmount() {
Money total = new Money(BigDecimal.ZERO, "CNY");
for (OrderItem item : items) {
total = total.add(item.calculateItemTotal());
}
// 应用折扣
return total.subtract(discount);
}
// 支付订单(业务逻辑)
public void pay() {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("只有已创建的订单可以支付,当前状态:" + status.getDescription());
}
this.status = OrderStatus.PAID;
this.paymentTime = LocalDateTime.now();
// 支付后再次校验订单状态
validateOrderState();
}
// 订单发货(业务逻辑)
public void ship() {
if (status != OrderStatus.PAID) {
throw new IllegalStateException("只有已支付的订单可以发货,当前状态:" + status.getDescription());
}
this.status = OrderStatus.SHIPPED;
this.shippingTime = LocalDateTime.now();
validateOrderState();
}
// 订单完成(确认收货)
public void complete() {
if (status != OrderStatus.SHIPPED) {
throw new IllegalStateException("只有已发货的订单可以完成,当前状态:" + status.getDescription());
}
this.status = OrderStatus.COMPLETED;
this.completionTime = LocalDateTime.now();
validateOrderState();
}
// 取消订单(业务逻辑)
public void cancel() {
if (status == OrderStatus.COMPLETED || status == OrderStatus.CANCELLED) {
throw new IllegalStateException("已完成或已取消的订单不能再次取消,当前状态:" + status.getDescription());
}
this.status = OrderStatus.CANCELLED;
this.cancelTime = LocalDateTime.now();
validateOrderState();
}
// 应用折扣(业务逻辑)
public void applyDiscount(Money discountAmount) {
Objects.requireNonNull(discountAmount, "折扣金额不能为空");
if (discountAmount.isNegative()) {
throw new IllegalArgumentException("折扣金额不能为负数");
}
Money totalAmount = calculateTotalAmount();
if (discountAmount.getAmount().compareTo(totalAmount.getAmount()) > 0) {
throw new IllegalArgumentException("折扣金额不能超过订单总金额");
}
this.discount = discountAmount;
}
// 添加商品项(业务逻辑)
public void addItem(Product product, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("商品数量必须大于0");
}
// 检查是否已存在该商品,存在则增加数量
for (OrderItem item : items) {
if (item.getProduct().getId().equals(product.getId())) {
// 注意:这里需要创建新的OrderItem对象,因为OrderItem是不可变的
OrderItem newItem = new OrderItem(product, item.getQuantity() + quantity);
items.remove(item);
items.add(newItem);
return;
}
}
// 不存在则新增
items.add(new OrderItem(product, quantity));
}
// 移除商品项(业务逻辑)
public void removeItem(String productId) {
items.removeIf(item -> item.getProduct().getId().equals(productId));
// 确保订单至少有一个商品项
if (items.isEmpty()) {
throw new IllegalStateException("订单不能移除所有商品项");
}
}
// 内部校验方法:确保订单状态和时间戳的一致性
private void validateOrderState() {
switch (status) {
case PAID:
if (paymentTime == null) {
throw new IllegalStateException("已支付订单必须有支付时间");
}
break;
case SHIPPED:
if (paymentTime == null || shippingTime == null) {
throw new IllegalStateException("已发货订单必须有支付时间和发货时间");
}
break;
case COMPLETED:
if (paymentTime == null || shippingTime == null || completionTime == null) {
throw new IllegalStateException("已完成订单必须有支付时间、发货时间和完成时间");
}
break;
case CANCELLED:
if (cancelTime == null) {
throw new IllegalStateException("已取消订单必须有取消时间");
}
break;
}
}
// Getter方法(部分关键属性提供只读访问)
public String getId() {
return id;
}
public String getCustomerId() {
return customerId;
}
public OrderStatus getStatus() {
return status;
}
public Money getDiscount() {
return discount;
}
public LocalDateTime getPaymentTime() {
return paymentTime;
}
public LocalDateTime getShippingTime() {
return shippingTime;
}
public LocalDateTime getCompletionTime() {
return completionTime;
}
public LocalDateTime getCancelTime() {
return cancelTime;
}
// 返回订单项的不可修改视图,保护内部状态
public List getItems() {
return Collections.unmodifiableList(items);
}
}
这个Java版本的充血模型实现具有以下特点:
使用这个订单实体时,外部代码只需调用其方法即可完成业务操作,无需关心内部状态管理和规则校验,体现了充血模型的核心优势。