DDD 中的通用存储库:如何使此接口通用?

Generic Repository within DDD: How can I make this interface generic?

我正在按照领域驱动设计原则开发多模块 CMS 应用程序。我正在尝试弄清楚如何实现通用存储库,从而避免大量样板代码。

想法是在持久性模块中实现“双向”映射策略(模型到实体,反之亦然)和通用存储库。此外,Domain 模块中的接口将充当 Domain 和 Persistence 之间的契约,因此我可以将其用于以后在其他层中的注入。

如何使这个界面通用?

具体来说,这里的问题是映射。由于我使用的是“双向”映射策略,域模块不知道数据库特定实体。

有没有办法在层之间映射通用类型模型?或者在保持层松散耦合的同时使用其他映射策略?

这是一个代码示例,用于阐明我要实现的目标。 这将是通用存储库的代码示例:

@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 特定注释 classes,因此使其松散耦合。我希望这个自定义存储库实现域模块的接口,允许我稍后在服务层中注入它。

public class CustomRepositoryImpl implements CustomRepository {

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

我怎样才能使这个 class 和这个接口通用,这样我就可以在模型和实体之间进行映射,因为域层没有关于实体 classes 的信息?

我终于想通了。

正如问题中所述,问题是层之间的映射。我创建了一个映射接口来声明映射方法。我使用 MapStruct 中的 @ObjectFactory 注释来处理通用映射 (look here):

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
}

然后我继续为每个子 classes 创建一个映射器,并使用 EntityMapper 接口扩展它,其中包含我想要映射的具体类型。

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

我创建了一个摘要 class,我在其中注入了 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.
}

然后我用这个抽象扩展了一个 ConcreteTypeRepositoryImpl class,并实现了一个通用接口,我以后可以在其他层中作为参考。

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 注入其他层并点击所需的存储库。