在DDD(领域驱动设计)架构中,Domain层 与Entity层的关系是核心业务逻辑与数据模型的结合点,二者的设计直接影响系统的可维护性和扩展性。以下是具体的设计原则和代码示例:
一、核心设计原则
-
Domain层主导业务逻辑
- Domain层包含聚合根(Aggregate Root) 、实体(Entity) 、值对象(Value Object) 和领域服务(Domain Service),是业务逻辑的集中地。
- Entity层(或基础设施层中的PO)仅负责数据持久化,是贫血模型(仅含属性和getter/setter)。
-
充血模型 vs 贫血模型
- 充血模型 :Entity(或聚合根)不仅包含数据,还封装与其相关的业务行为(如
order.cancel()
)。 - 贫血模型:Entity仅为数据容器,业务逻辑分散在Service中(不推荐)。
- 充血模型 :Entity(或聚合根)不仅包含数据,还封装与其相关的业务行为(如
-
依赖方向
- Domain层完全独立,不依赖任何外部框架(如Spring、JPA)。Entity层(PO)通过仓储接口(Repository)与Domain层交互,由基础设施层实现具体持久化逻辑。
二、代码示例:电商订单系统
1. Domain层设计(充血模型)
arduino
// 聚合根:Order(包含业务逻辑)
public class Order {
private String orderId; // 唯一标识
private List<OrderItem> items; // 值对象集合
private OrderStatus status;
// 业务行为:取消订单
public void cancel() {
if (status == OrderStatus.SHIPPED) {
throw new IllegalStateException("已发货订单不可取消");
}
this.status = OrderStatus.CANCELLED;
}
// 业务行为:添加商品项
public void addItem(Product product, int quantity) {
items.add(new OrderItem(product.getId(), product.getPrice(), quantity));
}
// 静态工厂方法(可选)
public static Order create(String orderId) {
return new Order(orderId, OrderStatus.CREATED);
}
}
// 值对象:OrderItem(不可变)
public class OrderItem {
private final String productId;
private final BigDecimal price;
private final int quantity;
public OrderItem(String productId, BigDecimal price, int quantity) {
this.productId = productId;
this.price = price;
this.quantity = quantity;
}
}
// 领域服务:复杂逻辑(跨聚合)
public class OrderService {
private final OrderRepository orderRepository;
public void processPayment(String orderId, Payment payment) {
Order order = orderRepository.findById(orderId);
order.markAsPaid(payment.getAmount());
orderRepository.save(order);
}
}
2. Entity层设计(贫血模型,用于持久化)
less
// JPA实体(数据表映射)
@Entity
@Table(name = "orders")
public class OrderEntity {
@Id
private String orderId;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@OneToMany(cascade = CascadeType.ALL)
private List<OrderItemEntity> items;
// 仅含getter/setter
}
// 值对象持久化
@Embeddable
public class OrderItemEntity {
private String productId;
private BigDecimal price;
private int quantity;
}
3. 仓储接口与实现
scss
// Domain层定义接口
public interface OrderRepository {
Order findById(String orderId);
void save(Order order);
}
// 基础设施层实现(依赖JPA)
@Repository
public class JpaOrderRepository implements OrderRepository {
@Autowired
private OrderJpaRepository jpaRepository; // Spring Data JPA接口
@Override
public Order findById(String orderId) {
OrderEntity entity = jpaRepository.findById(orderId).orElseThrow();
return convertToDomain(entity);
}
// 转换:Entity -> Domain
private Order convertToDomain(OrderEntity entity) {
Order order = new Order(entity.getOrderId(), entity.getStatus());
entity.getItems().forEach(item ->
order.addItem(item.getProductId(), item.getPrice(), item.getQuantity()));
return order;
}
}
三、关键实践
-
转换逻辑
- Domain层对象(如
Order
)与Entity层对象(如OrderEntity
)通过装配器(Assembler)或仓储实现转换,避免领域模型污染持久化细节。
- Domain层对象(如
-
事务边界
-
事务管理应在应用层(Application Service)完成,领域层仅关注业务规则。例如:
scss@Service public class OrderApplicationService { @Transactional public void cancelOrder(String orderId) { Order order = orderRepository.findById(orderId); order.cancel(); // 领域逻辑 orderRepository.save(order); } }
-
-
领域事件
-
领域行为可触发事件,由应用层发布:
csharppublic class Order { private List<DomainEvent> events; public void cancel() { this.status = OrderStatus.CANCELLED; events.add(new OrderCancelledEvent(this.orderId)); } public List<DomainEvent> getEvents() { return events; } }
-
四、分层架构对比
分层 | 职责 | 依赖关系 |
---|---|---|
Domain层 | 业务逻辑、领域模型 | 不依赖任何外部框架 |
Entity层(PO) | 数据持久化 | 依赖JPA/Hibernate等ORM |
基础设施层 | 实现仓储、消息队列等 | 依赖Domain层定义的接口 |
通过这种设计,Domain层保持业务纯净,Entity层仅作数据载体,二者通过明确的边界和转换逻辑解耦,符合DDD的高内聚、低耦合原则。