仓储模式 - 如何理解它以及如何与“复杂”实体一起使用?

87

我很难理解仓储模式。

关于这个主题有很多不同的观点,例如在Repository pattern done right中,也有其他观点,比如Repository is the new Singleton,或者像Don't use DAO use Repository,或者使用Spring JPA Data + Hibernate + MySQL + MAVEN,其中仓储似乎与DAO对象相同。

我厌倦了阅读这些文章,因为我认为它不应该是一个像这样被大量文章展示的复杂问题。

我的理解是:我想要的东西似乎是这样的:

         ------------------------------------------------------------------------
         |                            Server                                    |
         ------------------------------------------------------------------------
         |                    |                        |                        |
Client <-|-> Service Layer  <-|->  Repository Layer  <-|-> ORM / Database Layer |
         |                    |                        |                        |  
         ------------------------------------------------------------------------

服务层接受*DTO对象并将其传递给存储库层,后者基本上就是“那个人”,他知道如何存储实体。例如,假设您有一些工具的组合(请注意,这只是伪代码)。
@Entity
class ToolSet {
  @Id
  public Long id;
  @OneToOne
  public Tool tool1;
  @OneToOne
  public Tool tool2;
}

@Entity
class Tool {
  @Id
  public Long id;
  @OneToMany
  public ToolDescription toolDescription;
}

@Entity
class ToolDescription {
  @Id
  public Long id;
  @NotNull
  @OneToOne
  public Language language

  public String name;
  public String details;
}

我没有理解的部分是从客户端获取到一个 ToolSetDTO 对象的那一部分。
据我目前的理解,我可以编写一个具有方法 ToolSetRepository.save(ToolSetDTO toolSetDto)ToolSetRepository ,它“知道如何存储”一个 ToolSetDTO 。但是几乎每个教程都不会传递 *DTO 而是传递实体 Entity
让我困扰的是,如果您采用了上面提到的 ToolSet 示例,则需要执行以下步骤:
1.取出toolSetDto并检查是否非null 2.对于由toolSetDto拥有的每个tool*Dto: a)如果具有有效id,则从DTO转换为Entity;否则创建新的数据库条目 b)toolDescriptionDto进行转换/保存到数据库或创建新条目
3.在检查上述项目后创建ToolSet (entity) 并设置其以将其持久化到数据库中。
所有这些都太复杂了,不能简单地让服务功能(客户端的接口)处理此过程。
我所考虑的是创建一个例如 ToolSetRepository,但这里的问题是:
1.它接受一个ToolSet实体对象还是使用一个DTO对象? 2.无论如何:这个*Repository是否允许使用其他存储库对象?例如,如果我想保存ToolSet但必须先存储ToolToolDescription,那么我会在ToolSetRepository内部使用ToolRepositoryToolDescriptionRepository吗?如果可以的话:为什么不会破坏Repository模式?如果这个模式基本上是服务和我的ORM框架之间的一层,那么由于依赖性的原因,向其他*Repository类添加依赖关系就不“感觉对了”。
我不知道为什么我无法理解这一点。这听起来并不那么复杂,但仍有像Spring Data这样的帮助。另一件困扰我的事情是,我真的看不出这怎么会使任何事情变得更加容易。特别是我已经在使用Hibernate - 我看不到好处(但也许这是另一个问题)。
所以..我知道这是一个很长的问题,但我已经花费了几天的时间进行研究。我正在处理的现有代码已经开始变得混乱,因为我无法看透这个模式。

我希望有人能够给我一个比大多数文章和教程更全面的视角,这些文章和教程只停留在实现一个非常简单的存储库模式示例之上。


在我看来,ToolSetRepository 应该只知道 ToolSet 实体...... 而且在 ToolSet 上也可以使用 JaxB 注释,以将实体用作 DTO。 在客户端上,您仅拥有使用“?wsdl”从 Web 服务 URL 接收的 WSDL 生成的 jaxws clientgen 生成的 jaxb 类.... 然后在服务器端,您会接收到“未管理”的实体。 然后,您必须使用 entitymanager.merge 将其放入托管状态。 这就是所有的。 在我看来,仅在无法使用命名查询的复杂条件下需要特定存储库。 例如,标准 API 查询。 - StefanHeimberg
@StefanHeimberg 但是,例如ToolSetRepository如何处理ToolToolDescription的持久性?或者这些已经被持久化了吗?如果这些在此时应该已经被持久化,那么我应该在哪里做到这一点?在我的服务方法中执行此操作并不合适,因为像ToolSet这样的复杂实体会使服务方法代码膨胀。在我看来,服务方法只应该进行一些初始化和基本检查工作,然后将工作委托给下一层。 - Stefan Falk
1
据我所知,Hibernate(和JPA)是一个完整的DAO层,因为它的工作是连接到数据源(在这种情况下是数据库),而不管底层细节(MySQL、Oracle、SQL Server等),您可以以多种方式查询数据源。如果您想/需要为实体使用特定查询,它允许您使用标准,这些标准指定在存储库中使用,因此最终Hibernate既是Dao又是Repository。您将在其上创建自己的层来抽象化此dao(或repository)或您用于实现此操作的任何其他内容,并继续编程。 - Luiggi Mendoza
另一个要点:如果您说不想将实体的所有细节发送到客户端,则可以创建一个简单的“Info”(例如ToolSetInfo)对象,其中包含所有jaxb注释,然后在存储库内使用jpa构造函数表达式将数据从数据库填充到此“未管理”的对象中。 - StefanHeimberg
2
merge() 已经检查了是否为新数据,然后创建插入或更新查询。在我的看法中,这是底层 ORM 的责任,例如 JPA。 - StefanHeimberg
显示剩余13条评论
1个回答

122
您可以阅读我的“白痴仓库”post,了解存储库的简单原则。我认为您的问题在于您正在使用DTO,并且在这种情况下,您实际上并没有使用存储库模式,而是使用DAO。
存储库和dao之间的主要区别在于存储库仅返回由调用层理解的对象。大多数情况下,业务层使用存储库,因此它返回业务对象。 DAO返回可能是或可能不是整个业务对象的数据,即数据不是有效的业务概念。
如果您的业务对象只是数据结构,则可能是您存在建模问题,即设计不良。存储库与“丰富”的或至少正确封装的对象更相关。如果您只是加载/保存数据结构,则可能不需要存储库ORM就足够了。
如果您正在处理由其他对象(聚合)组成的业务对象,并且该对象需要所有部分才能保持一致(聚合根),那么存储库模式是最佳解决方案,因为它将抽象出所有持久性细节。您的应用程序只需请求“Product”,存储库将返回整个对象,无论恢复对象所需多少表格或查询。

根据您的代码示例,您没有“真正的”业务对象。您拥有Hibernate使用的数据结构。业务对象基于业务概念和用例设计。存储库使BL不必关心如何持久化该对象。在某种程度上,存储库充当对象和将被持久化的模型之间的“转换器/映射器”。基本上,repo将对象“缩小”到所需的持久性数据。

业务对象不是ORM实体。从技术角度来看,它可能是,但从设计角度来看,一个模型业务内容,另一个模型持久性内容。在许多情况下,这些不直接兼容。

最大的错误是根据存储需求和思维方式设计业务对象。与许多开发人员所认为的相反,ORM 的目的不是持久化业务对象,而是在关系型数据库上模拟“面向对象”数据库。ORM 映射是在您的数据库对象和表之间进行的,而不是在应用程序对象(尤其是业务对象)和表之间进行的。

1
嗨!现在我对仓库是什么有了更清晰的认识,但我想问一下关于我的具体代码示例:问题是我的客户端没有太多的操作,所以基本上只需要将例如ToolSet存储在数据库中。但这仍然不意味着我可以编写ToolSetRepository来构建我的实体类,然后将其持久化,这是正确的吗?最后一个重要问题是:“无论如何:*Repository是否允许在其中使用其他存储库对象?” - Stefan Falk
1
我看过的所有教程都没有展示更复杂的例子,其中存储库实际上必须执行人们声称它存在的任务。我只看到了非常简单的例子,让我想知道我需要这个做什么?^^ 除非允许在其他存储库对象中使用其他存储库对象以完成工作。 :) - Stefan Falk
4
“存储库模式实际上是接口的设计。一旦你有了接口,你就可以在实际实现方面自由发挥。关于使用其他存储库的存储库,我认为不行,那是错误的设计,这意味着你有两个处理同一对象的存储库。但一个存储库可以使用许多数据访问对象(DAOs)。在我的代码库中,存储库彼此不知道。问题是,你需要有一个合适的业务模型(具有清晰的一致性边界)才能正确地使用存储库,否则它只会增加复杂性。” - MikeSW
3
顺便提一句,这个仓库的目的是存储/检索东西。物品越复杂越好,但要记住重要的一点:该对象必须代表一个业务概念,可以是简单的(一个结构)或复杂的(有很多规则或许多子级)。 - MikeSW
1
不!存储库只是持久性问题。永远不要把诸如http之类的东西放在里面。你正在违反关注点分离原则。你真的想要一个对象来做所有的事情吗?存储库实现始终是持久性(作为一个概念)的一部分,你的应用程序应该仅通过注入使用抽象,即di容器应该创建仅从数据库中保存/加载的存储库,而不检查安全性。如果它们是查询的一部分,你可以“检查”它们,比如“select * from tools where id=@0 and userId=@1”。 - MikeSW
显示剩余12条评论

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