【问题标题】:Generic Repository within DDD: How can I make this interface generic?DDD 中的通用存储库:如何使此接口通用?
【发布时间】:2021-11-16 19:44:48
【问题描述】:

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

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

我怎样才能使这个接口通用?

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

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

这是一个代码示例,用于阐明我想要实现的目标。 这将是 Generic Repository 的代码示例:

@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;
    ...
}

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

【问题讨论】:

  • 我想我们需要看看你写的一些代码。我不清楚你需要什么“接口”才能成为“通用”。
  • 另外,你能更清楚你的目标是什么吗?双向映射表明在两个方向上都对域无知,并且实际上没有像操作存储库这样的事情,无论是否通用,而不赋予任何领域知识(至少,您将了解您想要的领域实体取回)。如果你真的认为你需要这种双向解耦,你也需要一个很好的理由去做。所以告诉我们你的理由。
  • 否则,显而易见的答案就是传递字符串。字符串是完全不可知的;由接收端解码它们。 个合法的用例。
  • 为什么需要这种“双向”映射?存储库层旨在将模型转换为实体,以便能够使用 ORM 并与数据库进行交互。我看不到这样的情况,您应该在没有存储库层抽象的情况下与该数据库进行交互。只有模型应该有生命周期。如果您需要在其他地方使用实体,它将引入耦合并严重违反 SOLID 原则。这违背了首先完成 DDD 过程的目的。
  • 嗨@RobertHarvey,感谢您的评论!我编辑了问题并提供了进一步的说明和示例代码。

标签: java spring-boot spring-data-jpa domain-driven-design ddd-repositories


【解决方案1】:

我终于明白了。

正如问题中所述,问题在于层之间的映射。我创建了一个映射接口来声明映射方法。我使用 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
}

然后我继续为每个子类创建一个映射器,并使用我想要映射的具体类型的 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.
}

然后我用这个抽象类扩展了一个 ConcreteTypeRepositoryImpl,并实现了一个通用接口,我以后可以将其用作其他层的引用。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 2012-09-15
    • 2021-09-15
    相关资源
    最近更新 更多