【问题标题】:Aggregate to JPA Entity mapping聚合到 JPA 实体映射
【发布时间】:2020-08-15 20:51:16
【问题描述】:

在我参与的一个DDD-项目中,我们正在寻找一些方便的解决方案,将entity objects 映射到domain objects,反之亦然。

该项目的开发人员同意将域模型与数据模型完全分离。 数据层使用JPA (Hibernate)作为持久化技术。

我们都认为持久性是 DDD 中的一个实现细节,从开发人员的角度来看,我们都在为应用程序的各个方面寻找最合适的解决方案。

我们最担心的是,当一个包含entities 列表的aggregate 映射到一个JPA entity 时,它又包含一个one-to-many 关系。

看看下面的例子:

领域模型

public class Product extends Aggregate {
    private ProductId productId;
    private Set<ProductBacklogItem> backlogItems;

    // constructor & methods omitted for brevity
}

public class ProductBacklogItem extends DomainEntity {
    private BacklogItemId backlogItemId;
    private int ordering;
    private ProductId productId;

    // constructor & methods omitted for brevity
}

数据模型

public class ProductJpaEntity {
    private String productId;
    @OneToMany
    private Set<ProductBacklogItemJpaEntity> backlogItems;

    // constructor & methods omitted for brevity
}

public class ProductBacklogItemJpaEntity {
    private String backlogItemId;
    private int ordering;
    private String productId;

    // constructor & methods omitted for brevity
}

存储库

public interface ProductRepository {        
    Product findBy(ProductId productId);
    void save(Product product);
}

class ProductJpaRepository implements ProductRepository {        
    @Override
    public Product findBy(ProductId productId) {
        ProductJpaEntity entity = // lookup entity by productId

        ProductBacklogItemJpaEntity backlogItemEntities = entity.getBacklogItemEntities();        
        Set<ProductBacklogItem> backlogItems = toBackLogItems(backlogItemEntities);

        return new Product(new ProductId(entity.getProductId()), backlogItems);
    }

    @Override
    public void save(Product product) {
        ProductJpaEntity entity = // lookup entity by productId

        if (entity == null) {
          // map Product and ProductBacklogItems to their corresponding entities and save
          return;
        }

        Set<ProductBacklogItem> backlogItems = product.getProductBacklogItems();
        // how do we know which backlogItems are: new, deleted or adapted...?
    }
}

ProductJpaEntity 已经存在于DB 中时,我们需要更新所有内容。 如果有更新,ProductJpaEntity 已经在 Hibernate PersistenceContext 中可用。 但是,我们需要弄清楚哪些ProductBacklogItems 被更改了。

更具体地说:

  • ProductBacklogItem 可以添加到 Collection
  • ProductBacklogItem 可能已从 Collection 中删除

每个ProductBacklogItemJpaEntity 都有一个Primary Key 指向ProductJpaEntity。 似乎检测新的或删除的ProductBacklogItems 的唯一方法是通过Primary Key 匹配它们。 但是,主键不属于域模型...

还有可能首先删除ProductJpaEntity 的所有ProductBacklogItemJpaEntity 实例(存在于数据库中),刷新到数据库,创建新的ProductBacklogItemJpaEntity 实例并将它们保存到数据库。 这将是一个糟糕的解决方案。每次保存 Product 都会导致 DB 中出现多个 deleteinsert 语句。

有哪些解决方案可以解决这个问题,而不会对领域和数据模型做出太多牺牲?

【问题讨论】:

  • 你的ProductBacklogItemJpaEntity 应该是一个@Entity 拥有自己的@PrimaryKey,然后当你收到Product 时,只需将ProductBacklogItem 列表转换为ProductBacklogItemJpaEntity 列表设置它进入ProductJpaEntity 并保存。当您调用存储库save 方法时,如果Product 域对象中的列表包含所有积压项目,则此方法有效(剧透,这是必须的)。如果这不是您的情况,则需要有关何时使用 save 方法的一些详细信息才能做出响应。

标签: java jpa domain-driven-design ddd-repositories


【解决方案1】:

这是Blaze-Persistence Entity Views 的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

实体视图也可以是可更新和/或可创建,即支持刷新更改,这可以用作 DDD 设计的基础。 可更新实体视图实现脏状态跟踪。您可以内省实际更改或刷新更改的值。

您可以将可更新的实体视图定义为抽象类以隐藏“实现细节”,例如protected修饰符后面的主键如下:

@UpdatableEntityView
@EntityView(ProductJpaEntity.class)
public abstract class Product extends Aggregate {
    @IdMapping
    protected abstract ProductId getProductId();
    public abstract Set<ProductBacklogItem> getBacklogItems();
}
@UpdatableEntityView
@EntityView(ProductBacklogItemJpaEntity.class)
public abstract class ProductBacklogItem extends DomainEntity {
    @IdMapping
    protected abstract BacklogItemId getBacklogItemId();
    protected abstract ProductId getProductId();
    public abstract int getOrdering();
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

Product p = entityViewManager.find(entityManager, Product.class, id);

保存,即刷新更改也很容易

entityViewManager.save(entityManager, product);

Spring Data 集成允许您像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features,对于刷新更改,您可以在您的存储库中定义一个接受可更新实体视图的 save 方法

【讨论】:

  • 该库看起来很有趣,但在上面的示例中,我看到了问题,视图类仍将通过注释依赖于 Jpa 类。问题方法(以及洋葱架构背后)的想法是避免这种依赖关系。
  • 你想如何避免依赖?在某些时候,您必须以某种方式连接模型。由于此依赖关系仅在注释级别,您可以使用 runtime 依赖范围,因此您的域模型的消费者不需要依赖您的持久性模型。
猜你喜欢
  • 2011-07-13
  • 1970-01-01
  • 2012-04-06
  • 2021-09-13
  • 1970-01-01
  • 2011-10-20
  • 2020-03-21
  • 2011-07-25
  • 2015-08-02
相关资源
最近更新 更多