在DDD架构中,如何设计Domain层与Entity层的关系?

在DDD(领域驱动设计)架构中,​Domain层Entity层的关系是核心业务逻辑与数据模型的结合点,二者的设计直接影响系统的可维护性和扩展性。以下是具体的设计原则和代码示例:


一、核心设计原则

  1. Domain层主导业务逻辑

    • Domain层包含聚合根(Aggregate Root)​实体(Entity)​值对象(Value Object)​领域服务(Domain Service)​,是业务逻辑的集中地。
    • Entity层(或基础设施层中的PO)仅负责数据持久化,是贫血模型(仅含属性和getter/setter)。
  2. 充血模型 vs 贫血模型

    • 充血模型 :Entity(或聚合根)不仅包含数据,还封装与其相关的业务行为(如order.cancel())。
    • 贫血模型:Entity仅为数据容器,业务逻辑分散在Service中(不推荐)。
  3. 依赖方向

    • 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;
    }
}

三、关键实践

  1. 转换逻辑

    • Domain层对象(如Order)与Entity层对象(如OrderEntity)通过装配器(Assembler)​或仓储实现转换,避免领域模型污染持久化细节。
  2. 事务边界

    • 事务管理应在应用层(Application Service)​完成,领域层仅关注业务规则。例如:

      scss 复制代码
      @Service
      public class OrderApplicationService {
          @Transactional
          public void cancelOrder(String orderId) {
              Order order = orderRepository.findById(orderId);
              order.cancel(); // 领域逻辑
              orderRepository.save(order);
          }
      }
  3. 领域事件

    • 领域行为可触发事件,由应用层发布:

      csharp 复制代码
      public 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的高内聚、低耦合原则。

相关推荐
间彧4 小时前
DTO和VO在实际项目中如何避免重复定义?有哪些自动化转换工具推荐?
后端
间彧4 小时前
SpringBoot项目各层级的详细作用与区分
后端
00后程序员4 小时前
Swoole HTTPS 实战,在生产环境部署、性能权衡与排查流程
后端
程序员爱钓鱼4 小时前
Python编程实战 · 基础入门篇 | 什么是Python
后端·python
Mintopia4 小时前
⚡当 Next.js 遇上实时通信:Socket.io 与 Pusher 双雄传
前端·后端·全栈
ZhengEnCi4 小时前
ObjectUtils.isEmpty 完全指南-从入门到精通的 Java 空值判断利器
java·后端
凯哥19704 小时前
Supabase Edge Functions 开发指南
后端
tangdou3690986554 小时前
可怕!我的Nodejs系统因为日志打印了Error 对象就崩溃了😱 Node.js System Crashed Because of Logging
前端·javascript·后端
廖广杰4 小时前
Oauth2.0 授权码模式认证流程
后端