在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的高内聚、低耦合原则。

相关推荐
国服第二切图仔9 小时前
Rust入门开发之Rust中如何实现面向对象编程
开发语言·后端·rust
Mos_x9 小时前
15.<Spring Boot 日志>
java·后端
William_cl9 小时前
【ASP.NET MVC 进阶】DataAnnotations 特性验证全解析:从基础到避坑,让数据校验像 “安检“ 一样靠谱
后端·asp.net·mvc
SimonKing9 小时前
你的项目还在用MyBatis吗?或许这个框架更适合你:Easy-Query
java·后端·程序员
货拉拉技术9 小时前
从代码到配置:如何用SQL配置实现数据核对
java·后端
xuejianxinokok9 小时前
可能被忽略的 pgvector 各种坑
数据库·后端
用户345675638389 小时前
Python+Requests零基础系统掌握接口自动化测试
后端
肖文英10 小时前
Java类型概览
后端
武子康10 小时前
大数据-144 Apache Kudu:实时写 + OLAP 的架构、性能与集成
大数据·后端·nosql
程序员小假10 小时前
设计模式了解吗,知道什么是饿汉式和懒汉式吗?
java·后端