对Spring-Data DDD仓储模式感到困惑

21

我对DDD存储库模式不是很了解,但Spring中的实现让我感到困惑。

public interface PersonRepository extends JpaRepository<Person, Long> { … }

如果您从一个数据库更改到另一个数据库,则需要同样更改接口,因为该接口扩展了JpaRepository(或MongoDBRepository...)。

对于我来说,接口存在的目的是为了提供一些抽象概念,但在这里它并不是很抽象...

您知道为什么Spring-Data会这样工作吗?

3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
11

你说得对,接口是关于某个在外部透视下所有实现类都等效运行的抽象。

而这正是这里所发生的:

  • JpaRepository 是您所有 JPA 存储库(针对不同实体)的共同视图,而 MongoDBRepository 则是所有 MongoDB 实体的相同视图。
  • 但是除了它们的共同超级接口中定义的内容之外,JpaRepository 和 MongoDBRepository 没有任何共同点:

    • org.springframework.data.repository.PagingAndSortingRepository
    • org.springframework.data.repository.Repository

所以对我来说看起来很正常。

如果您使用实现存储库的类,则使用 PagingAndSortingRepository 或 Repository,如果要能够从 JPA 实现切换到基于文档的实现(抱歉,但我无法想象这样的用例 - 无论如何)。当然,根据它是什么,您的存储库实现应该实现正确的接口(JpaRepository、MongoDBRepository)。


2
实际上,如果您知道接口中的存储库实现是给定类型,为什么还要使用接口呢?但我猜像您这样做更难... - Sebastien Lorber
我认为/期望从关系型数据库(RDBMS)转变到面向文档的存储(Document-oriented store)需要在对象/领域模型上进行重大的更改。无论如何,持久性抽象总是会以某种方式泄漏出来。这是一直存在的现象。如果可以仅仅更改从JpaRepository到MongoDBRepository而无需对领域模型进行重大修改,那么我会推测该领域模型本身并不是很复杂。这种情况没有错,因为有些应用程序就是如此。 - luis.espinal

8
这背后的原因在这篇博客文章中已经很明确地阐述了:http://blog.springsource.com/2011/02/10/getting-started-with-spring-data-jpa/。 定义这个接口有两个目的:首先,通过扩展JpaRepository,我们将一些通用的CRUD方法添加到我们的类型中,允许保存账户、删除它们等等。其次,这将允许Spring Data JPA存储库基础设施扫描类路径以查找此接口,并为其创建一个Spring bean。 如果您不信任如此接近源头的资源(双关语),那么阅读这篇文章也是一个好主意:http://www.brucephillips.name/blog/index.cfm/2011/3/25/Using-Spring-Data-JPA-To-Reduced-Data-Access-Coding。 我不需要编写PersonRepository接口的实现。Spring将创建该接口的实现,并使PersonRepository bean可供自动装配到我的Service类中。PersonRepository bean将具有所有标准的CRUD方法(这将是事务性的)并返回Person对象或Person对象集合。因此,通过使用Spring Data JPA,我已经避免了编写自己的实现类。

1
我不关心实现细节。如果仓库只需要被扫描,为什么不扩展一个更常见的仓库接口呢?他们谈论保存和删除,我对mongoDB不是很了解,但我猜我们也可以在mongoDB上执行这些操作... 如果你向服务公开flush等方法,那么你就让它们依赖于ORM了,对吧? - Sebastien Lorber

3
直到Spring Data的M2版本,由于以下原因,我们要求用户扩展JpaRepository: 1.类路径扫描基础设施仅拾取扩展该接口的接口,因为可能会同时使用Spring Data JPA和Spring Data Mongo,并且将它们都指向同一个包,因此无法确定要为哪个存储创建代理。但是自RC1以来,我们只是将这个负担留给开发人员,因为我们认为这是一个相当奇特的情况,而仅仅使用Repository、CrudRepository或类似的内容的好处超过了您在刚才描述的角落案例中所需付出的努力。您可以使用命名空间中的exclude和include元素来获得更精细的控制。 2.直到M2,通过重新声明CRUD方法并用@Transactional进行注释,将事务应用于CRUD方法。这个决定反过来又是由算法AnnotationTransactionAttributeSource驱动的,该算法用于查找事务配置。因为我们希望为用户提供重新声明具体存储库接口中的CRUD方法并在其上应用@Transactional即可重新配置事务的可能性。对于RC1,我们决定实现一个自定义的TransactionAttributeSource,以便能够将注释移回存储库CRUD实现。 长话短说,归结如下: 从RC1开始,不再需要扩展特定存储库接口,除非您想要: 1.在更核心的存储库接口中使用基于List的访问来查找所有内容(虽然您也可以简单地在公共基础接口中重新声明相关方法以返回List)。 2.使用JPA特定的方法,如saveAndFlush等。 通常,自RC1以来,您在暴露CRUD方法方面更加灵活,因为您甚至只需扩展Repository标记接口并选择性地添加要公开的CRUD方法。由于支持实现仍将实现PagingAndSortingRepository的所有方法,因此我们仍然可以将调用路由到实例:
public interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

  List<T> findAll();

  T findOne(ID id);
}

public interface UserRepository extends MyBaseRepository<User, Long> {

  List<T> findByUsername(String username);
}
在这个例子中,我们定义了MyBaseRepository,只公开findAll()findOne(...)(它们将被路由到实现CRUD方法的实例),而具体的存储库则向这两个CRUD方法添加一个查找器方法。 要了解更多详细信息,请参阅参考文档

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,