Агрегирование в JPA Entity mapping - PullRequest
0 голосов
/ 01 мая 2020

В DDD -проекте, в который я участвую, мы ищем несколько удобных решений для сопоставления entity objects с domain objects и наоборот.

Разработчики этого проекта полностью согласились отделить модель предметной области от модели данных. Уровень данных использует JPA (Hibernate) в качестве технологии персистентности.

Поскольку мы все считаем, что персистентность - это деталь реализации в DDD, с точки зрения разработчиков, мы все ищем для наиболее подходящего решения в каждом аспекте приложения.

Наибольшую проблему мы имеем, когда aggregate, содержащий список entities, сопоставляется с 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. Однако первичные ключи не принадлежат модели домена ...

Также существует возможность сначала удалить все ProductBacklogItemJpaEntity экземпляров (которые присутствуют в БД) из ProductJpaEntity, flu sh в БД, создайте новые ProductBacklogItemJpaEntity экземпляры и сохраните их в БД. Это было бы плохим решением. Каждое сохранение Product приведет к нескольким операторам delete и insert в БД.

Какое решение существует для решения этой проблемы, не жертвуя при этом слишком многими жертвами в модели Domain & Data?

1 Ответ

0 голосов
/ 02 мая 2020

Это идеальный вариант использования для Blaze-Persistence Entity Views .

Я создал библиотеку, позволяющую легко сопоставлять модели JPA и пользовательский интерфейс или модели, определенные в абстрактном классе, что-то вроде Весенние данные прогнозы на стероиды. Идея состоит в том, что вы определяете свою целевую структуру (модель домена) так, как вам нравится, и сопоставляете атрибуты (получатели) посредством выражений JPQL с моделью сущности.

Представления сущностей также могут быть обновляемыми и / или Создаваемый , т.е. поддерживает возврат изменений, который может быть использован как основа для DDD дизайна. Обновляемые представления сущностей реализуют отслеживание грязного состояния. Вы можете анализировать фактические изменения или грипп sh измененные значения.

Вы можете определить свои обновляемые представления сущностей как абстрактные классы, чтобы скрыть «особенности реализации», например, первичный ключ за защищенным модификатором, например:

@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();
}

Запрос - это применение представления сущности к запросу, простейшим из которого является просто запрос по идентификатору.

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

Сохранение, т. Е. Сброс изменений а также

entityViewManager.save(entityManager, product);

Интеграция Spring Data позволяет использовать его почти так же, как проекции Spring Data: https://persistence.blazebit.com/documentation/1.4/entity-view/manual/en_US/#spring -data-features и для сброса изменений, вы можете определить save метод в вашем хранилище, который принимает обновляемый вид сущности

...