Универсальный репозиторий в DDD: Как я могу сделать этот интерфейс универсальным?

#java #spring-boot #spring-data-jpa #domain-driven-design #ddd-repositories

Вопрос:

Я разрабатываю многомодульное приложение CMS, следуя принципам проектирования, основанным на домене. Я пытаюсь понять, как реализовать универсальный репозиторий, таким образом избегая большого количества кода котельной плиты.

Идея состоит в том, чтобы иметь «двустороннюю» стратегию сопоставления (модель с сущностью и наоборот) и универсальный репозиторий, реализованный в модуле сохранения. Кроме того, интерфейс в модуле домена будет действовать как контракт между доменом и сохраняемостью, поэтому я могу использовать его для последующего внедрения на других уровнях.

Как я могу сделать этот интерфейс универсальным?

Если быть точным, проблема здесь заключается в отображении. Поскольку я использую стратегию «двухстороннего» сопоставления, модуль домена понятия не имеет о конкретных сущностях БД.

Есть ли способ сопоставить модели универсального типа между слоями? Или используйте какую-то другую стратегию отображения, сохраняя слои слабо связанными?

Вот пример кода, чтобы прояснить, чего я пытаюсь достичь. Это был бы пример кода для универсального репозитория:

 @MappedSuperclass
public abstract class AbstractJpaMappedType {
  …
  String attribute
}

@Entity
public class ConcreteJpaType extends AbstractJpaMappedType { … }

@NoRepositoryBean
public interface JpaMappedTypeRepository<T extends AbstractJpaMappedType>
  extends Repository<T, Long> {

  @Query("select t from #{#entityName} t where t.attribute = ?1")
  List<T> findAllByAttribute(String attribute);
}

public interface ConcreteRepository
  extends JpaMappedTypeRepository<ConcreteType> { … }
 

Кроме того, я хочу создать свой собственный пользовательский репозиторий, чтобы иметь возможность сопоставлять модель с сущностью и наоборот, поэтому у меня не было бы аннотаций JPA в моих доменных классах, что делает его слабо связанным. Я хочу, чтобы в этом пользовательском репозитории был реализован интерфейс из модуля домена, позволяющий мне внедрить его позже на уровне служб.

 public class CustomRepositoryImpl implements CustomRepository {

    public final JpaMappedTypeRepository<T> repository;
    ...
}
 

Как я могу сделать этот класс и этот интерфейс универсальными, чтобы я мог выполнять сопоставление между моделью и сущностью, поскольку уровень домена не содержит информации о классах сущностей?

Комментарии:

1. Я думаю, нам нужно будет посмотреть какой-нибудь код, который вы написали. Мне непонятно, какой «интерфейс» вам нужен, чтобы быть «универсальным».

2. Кроме того, не могли бы вы немного яснее сформулировать свои цели? Двустороннее сопоставление предполагает незнание домена в обоих направлениях, и на самом деле нет такой вещи, как управление репозиторием, общим или нет, без предоставления каких-либо знаний о домене вообще (по крайней мере, у вас будет знание сущности домена, которую вы хотите получить). Если вы действительно думаете, что вам нужна такая двусторонняя развязка, вам также нужна веская причина для этого. Так что расскажите нам о своих рассуждениях.

3. В противном случае очевидный ответ состоит в том, чтобы просто передавать строки. Строки полностью агностичны; их декодирование будет зависеть от принимающей стороны. Для этого существуют законные варианты использования.

4. Почему существует необходимость в таком «двустороннем» отображении? Уровень репозитория предназначен для преобразования модели в объект, чтобы иметь возможность использовать ORM и взаимодействовать с базой данных. Я не вижу такого случая, когда вы должны взаимодействовать с этой базой данных без абстракции уровня репозитория. Только модель должна иметь жизненный цикл. Если вам нужно использовать объект в другом месте, это приведет к связыванию и сильно нарушит принципы SOLID. Это в первую очередь противоречит цели прохождения процесса DDD.

5. Привет @RobertHarvey, спасибо за комментарий! Я отредактировал вопрос и предоставил дополнительные разъяснения и пример кода.

Ответ №1:

В конце концов я это понял.

Проблема, как было указано в вопросе, заключалась в отображении между слоями. Я создал интерфейс сопоставления для объявления методов сопоставления. Я использовал @ObjectFactory аннотацию из MapStruct для работы с общим отображением (смотрите здесь).:

 public interface EntityMapper<M, E> {
    M toModel(E entity);
    List<M> toModelList(List<E> entities);
    E toEntity(M model);
    List<E> toEntityList(List<M> models);

    // default object factory methods
}
 

Затем я приступил к созданию картографа для каждого из дочерних классов и расширил его с помощью интерфейса EntityMapper конкретными типами, которые я хочу сопоставить.

 @Mapper(componentModel="spring")
public interface ConcreteEntityMapper extends EntityMapper<ConcreteModelType, ConcreteJpaType> {
}
 

Я создал абстрактный класс, в который ввел репозиторий JPA и картограф, а также реализовал общие методы.

 abstract class CustomRepositoryImpl<T extends AbstractModelMappedType, E extends AbstractJpaMappedType> {

    private final JpaMappedTypeRepository<E> repository;

    private final EntityMapper<M, E> mapper;

    //... common methods for mapping and querying repositories.
}
 

Затем я расширил конкретный пример с помощью этого абстрактного класса и реализовал универсальный интерфейс, который позже я смогу использовать в качестве ссылки на других уровнях.

 public interface CustomRepository<M> {

    M saveOrUpdate(M model);
    Optional<M> getById(Long id);
    List<M> getByName(String name);
    List<M> getAll();
    void delete(Long id);
}
 
 @Component
public class ConcreteTypeRepositoryImpl extends CustomRepositoryImpl<ConcreteModelType,ConcreteJpaType> implements CustomRepository<ConcreteModelType> {

    public ConcreteTypeRepositoryImpl(JpaMappedTypeRepository<ConcreteJpaType> repository,
                                  EntityMapper<ConcreteModelType, ConcreteJpaType> mapper) {
        super(repository, mapper);
    }
}
 

И это было бы все. Теперь я могу внедрить CustomRepository в другие слои и попасть в нужный репозиторий.